I am new to both d3 and web programming generally. I have put together a force layout graph based on https://gist.github.com/mbostock/1153292. The graph works fine in Safari, Chrome and Opera (I haven't checked IE yet).However when I try to use it in Firefox I get the error "Tick is not defined".I am using Firefox 12.
Any advice on this would be much appreciated
Thanks,
Claire
(The code is a js script file and is triggered on a mouse click, the force layout part is below.).
d3.csv("data/sharing.csv?r1", function(error, data) {
dataset = data
var nodes = {};
dataset.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name:link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
var w = 500;
var h = 600;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(dataset)
.size([w-10,h-10])
.linkDistance(60)
.charge(-375)
.on("tick", tick)
.start();
//Draw svg canvas
var svg = d3.select("#svgContainer").append("svg").attr("id", "viz").attr("width", w).attr("height", h)
// Create arrowheads
svg.append("svg:defs").selectAll("marker")
.data(["end-arrow"])
.enter()
.append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.attr("fill", "black")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
//Add links between the nodes and draw arrowhead at end of it.
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter()
.append("svg:path")
.attr("stroke-width",2)
.attr("stroke", "black")
.attr("fill","none")
.attr("marker-end", "url(#end-arrow)");
//Draw circles for nodes
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter()
.append("svg:circle")
.attr("r", 6)
.attr("fill", "white")
.attr("stroke", "black")
.call(force.drag)
.on("mouseover", fade(.1))
.on("mouseout", fade(1))
//Label the nodes/circles
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter()
.append("svg:g")
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; })
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
=============REPLY TO COMMENT == FULL SCRIPT INCLUDING CALL TO CSV===
//If sharing button is clicked, load sharing data
d3.select("#sharing").on("click", function() {
d3.csv("data/sharing.csv?r1", function(error, data) {
if (error)
{//If error is not null,(i.e : something goes wrong), log the error.
window.console.log(error);
}
else
{//If file loaded correctly, log the data to the console.
dataset = data
window.console.log(dataset)
color = getColor()
vizType = "force";
//Hide date fields/buttons as they are not applicable
d3.select("#instructions").classed("hidden", true);
d3.select("#instructions2").classed("hidden", false);
d3.select("#startLabel").classed("hidden", true);
d3.select("#startDate").classed("hidden", true);
d3.select("#endLabel").classed("hidden", true);
d3.select("#endDate").classed("hidden", true);
d3.select("#removeFilter").classed("hidden", true);
d3.select("#sharing").classed("hidden", true);
d3.select("#showData").classed("hidden", false);
d3.select("#showData").attr("value", "Back to Circles Vizualization");
d3.select("#tipsData").classed("hidden", true);
d3.select("#ncpData").classed("hidden", true);
d3.select("#tipsNCPData").classed("hidden", true);
d3.select("#tipsLabel").classed("hidden", true);
d3.select("#ncpLabel").classed("hidden", true);
d3.select("#tipsNCPLabel").classed("hidden", true);
//Clear the previous viz and data
d3.select("#viz").remove();
d3.select("#stageTable").remove();
d3.select("#userTable").remove();
//Gets a count of sender records/source and stage/type
var senderCount = getSortingCount(dataset,"Sender");
var stageCount = getSortingCount(dataset,"Stage");
//create tables summarising results
var summarySenderTable = tabulate(senderCount, ["Shared", "Sender"], vizType);
var summaryStageTable = tabulate(stageCount, ["Shared", "Stage"], vizType);
var nodes = {};
// For each datapoint, check if a node exists already, if not create a new one.
dataset.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] ={name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
//Set the width and height for the svg, that will display the viz
var w = 500;
var h = 600;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(dataset)
.size([w-10,h-10])
.linkDistance(60)
.charge(-375)
.on("tick", tick)
.start();
//Draw svg
var svg = d3.select("#svgContainer").append("svg")
.attr("id","viz").attr("width",w).attr("height", h)
// Create arrowheads
svg.append("svg:defs").selectAll("marker")
.data(["end-arrow"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.attr("fill", "black")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
//Add links between the nodes and draw arrowhead at end of it.
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter()
.append("svg:path")
.attr("stroke-width",2)
.attr("stroke", function(d){return color(d.ScreenName)})
.attr("fill","none")
.attr("marker-end", "url(#end-arrow)");
//Draw circles for nodes
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter()
.append("svg:circle")
.attr("r", 6)
.attr("fill", "white")
.attr("stroke", "black")
.call(force.drag)
.on("mouseover", fade(.1))
.on("mouseout", fade(1))
//Label nodes/circles
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter()
.append("svg:g")
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; })
//Set radius for arrows and applies transform
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
//Allow for filter by row on stageTable
d3.select("#stage").select("#stageTable").selectAll("tr")
.on("click", function(d){
d3.select(this)
var rowText = this.childNodes[1].innerHTML
var svg = d3.select("#svgContainer").select("svg")
var path = svg.selectAll("path")
.style ("opacity", 1)
.transition()
.duration(250)
.style ("opacity", function(d){
if(d.ScreenName == rowText){
d3.selectAll("marker path").transition().style("stroke-opacity", 1);
return fade(1)
}
else{
d3.selectAll("marker path").transition().style("stroke-opacity", 0.1);
return 0.1
})
d3.select("#removeFilter").classed("hidden", false);
})
//Checks what links are connected to which(used for mouseover)
var linkedByIndex = {};
dataset.forEach(function(d) {linkedByIndex[d.source.index + "," + d.target.index] = 1;});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
//Fades in/out circles and arrows on mouseover.
function fade(opacity) {
return function(d) {
circle.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
path.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
}
}
})
})
Accessor for colour
function getColor(){
return color
}
Seeing the entire source code helped to clarify things. There is an if/else statement at the very top that checks for an error. The entire rest of the code is inside the else block. This is what's causing the problem.
Function declarations (such as tick() in your case) have browser-specific weird behaviour when defined inside conditional blocks. Here's a pretty good write-up that explains the differences between function declarations, function expressions and the ill-defined and inconsistently supported function statements (which is what you've inadvertently created with so much code living in an else block).
If you pull the code out of the else block, I think the behavior should be more predictable across browsers.
In general, it's not good programming practice to create enormous, long conditional blocks. Not only does it introduce the possibility of these types of errors but it can be very difficult to read and understand. Same thing goes for very deeply nested conditions.
Try to keep your conditions fairly tight so that the code living inside the conditional blocks corresponds directly to the meaning of the condition itself. You should be able to read the intention of condition and block contents out loud and they should make sense together. As much as possible, code that doesn't have to do with the condition should be at the top level of the function containing it. You can increase readability by factoring your code into meaningful functions and keeping conditions under control.
In your example above, you could do:
if (error) {
window.console.log(error);
}
else {
window.console.log(dataset);
}
dataset = data
color = getColor()
vizType = "force";
...
... rest of code
One final comment is that a tool like JSLint or JSHint to validate your code. It would point out problems like this automatically. It can be overly strict sometimes but its a good learning experience to at least understand what it's complaining about.
Related
If you see the existing code, https://jsfiddle.net/sheilak/9wvmL8q8 when the graph is loaded for first time links that connecting the parent and child node are from border of parent node but once its collapsed and expanded, you can see same links are from center of parent node. i don't want to link to be from center of the parent node.
code
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.size([width, height])
//gravity(0.2)
.linkDistance(height / 6)
.charge(function(node) {
if (node.type !== 'ORG') return -2000;
return -30;
});
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", function(d) {
return d;
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 12)
.attr("refY", 0)
.attr("markerWidth", 9)
.attr("markerHeight", 5)
.attr("orient", "auto")
.attr("class", "arrow")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var json = dataset;
var edges = [];
json.edges.forEach(function(e) {
var sourceNode = json.nodes.filter(function(n) {
return n.id === e.from;
})[0],
targetNode = json.nodes.filter(function(n) {
return n.id === e.to;
})[0];
edges.push({
source: sourceNode,
target: targetNode,
value: e.Value
});
});
for(var i = 0; i < json.nodes.length; i++) {
json.nodes[i].collapsing = 0;
json.nodes[i].collapsed = false;
}
var link = svg.selectAll(".link");
var node = svg.selectAll(".node");
force.on("tick", function() {
// make sure the nodes do not overlap the arrows
link.attr("d", function(d) {
// Total difference in x and y from source to target
diffX = d.target.x - d.source.x;
diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
offsetX = (diffX * d.target.radius) / pathLength;
offsetY = (diffY * d.target.radius) / pathLength;
return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
update();
function update(){
var nodes = json.nodes.filter(function(d) {
return d.collapsing == 0;
});
var links = edges.filter(function(d) {
return d.source.collapsing == 0 && d.target.collapsing == 0;
});
force
.nodes(nodes)
.links(links)
.start();
link = link.data(links)
link.exit().remove();
link.enter().append("path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
node = node.data(nodes);
node.exit().remove();
node.enter().append("g")
.attr("class", function(d) {
return "node " + d.type
});
node.append("circle")
.attr("class", "circle")
.attr("r", function(d) {
d.radius = 30;
return d.radius
}); // return a radius for path to use
node.append("text")
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("class", "text")
.text(function(d) {
return d.type
});
// On node hover, examine the links to see if their
// source or target properties match the hovered node.
node.on('mouseover', function(d) {
link.attr('class', function(l) {
if (d === l.source || d === l.target)
return "link active";
else
return "link inactive";
});
});
// Set the stroke width back to normal when mouse leaves the node.
node.on('mouseout', function() {
link.attr('class', "link");
})
.on('click', click);
function click(d) {
if (!d3.event.defaultPrevented) {
var inc = d.collapsed ? -1 : 1;
recurse(d);
function recurse(sourceNode){
//check if link is from this node, and if so, collapse
edges.forEach(function(l) {
if (l.source.id === sourceNode.id){
l.target.collapsing += inc;
recurse(l.target);
}
});
}
d.collapsed = !d.collapsed;
}
update();
}
}
There are two simple ways to address this that require little modification of your existing code.
The first is half done as the target of each link is already offset when you define your path data:
return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});
You could extend this to offset from the source node quite easily, just add the offsets to sourceX and sourceY as here. This way it doesn't matter if the nodes are above or under the links because they don't overlap. (There might be slight overlap, so you could add a pixel or two to the offsets to account for link width).
The second option is possibly easier in d3v4+, as it features selection.raise() (docs). This method raises the selected item to the top of the SVG (as the last child of the parent element). The is equivalent to:
this.parentNode.appendChild(this);
In your click function, after you update the graph, we can use this line to ensure the node that was clicked on rises to the top (over the links). Here's an example of that.
As a follow up question of D3.js change width of container after it is drawn I create the rectangles that fits the text length, I want to link the rectangles from bottom. But I'm stuck in getting the width of rectangle when I draw the link.
This is the js code:
var rectW = 140, rectH = 40;
// Declare the nodes.
var node = draw.selectAll('g.node')
.data(nodes, function(d) { return d.id; });
// Enter the nodes.
var nodeLabel = node.enter().append('g')
.attr('transform', function(d) { return 'translate(' + source.x0 + ',' + source.y0 + ')'; });
var nodeRect = nodeLabel.append('rect')
.attr('width', rectW)
.attr('height', rectH);
var nodeText = nodeLabel.append('text')
.attr('x', rectW / 2)
.attr('y', rectH / 2)
.text(function (d) { return d.name; });
// This arranges the width of the rectangles
nodeRect.attr("width", function() {
return this.nextSibling.getComputedTextLength() + 20;
})
// This repositions texts to be at the center of the rectangle
nodeText.attr('x', function() {
return (this.getComputedTextLength() + 20) /2;
})
Next,I'd like to link the nodeRects. Linking the top left corner is ugly, so I adjust a bit:
link.attr('d', function (d) {
var sourceX = d.source.x + 0.5*d.source.getComputedTextlength() + 10,
sourceY = (d.source.y > d.target.y)? d.source.y: (d.source.y + rectH),
targetX = d.target.x + 0.5*d.target.getComputedTextlength() +10,
targetY = (d.source.y >= d.target.y)? (d.target.y + rectH) : d.target.y;
It returns error. Is there a way that I can get access to the target rect and source rect's textlength or width?
I find an answer by myself. d.source.width doesn't work because it is not defined.
Change
nodeRect.attr("width", function() {
return this.nextSibling.getComputedTextLength() + 20;
})
to
nodeRect.attr("width", function(d) {
d.width = this.nextSibling.getComputedTextLength() + 20;
return d.width;
})
Then use d.source.width works well.
I created tree layout as in JSFiddle example http://jsfiddle.net/oo66o0q0/15/.
Requirement is path should highlighted in red and with extra width when user click on node's right click menu "Highlight Route" option.
This is working in chrome correctly but in IE highlighted route color becomes black.
If I remove markers then it works in IE as well.
How to resolve this issue in IE but not removing markers?
function treeInitialize(graphData){
diagramLayout = d3.select("#diagramLayout")
.attr("id", "diagramLayout")//set id
.attr("width", width)//set width
.attr("height", height)//set height
.append("g")
.attr("transform", "translate(" + 20 + "," + 20 + ")")
markerRefx = 40;
var data2 = graphData.links.filter(function(l){
if(l.target == undefined && l.source == undefined){
return false;
}else{
return true;
}
});
data2.push(JSON.parse('{"target":"glossforArrow","source":""}'))
var treeData = d3.stratify().id(function(d){ return d.target; }).parentId(function(d) {
return d.source;
})(data2)
nodes = d3.hierarchy(treeData, function(d) {return d.children;});
var levelWidth = [1];
var childCount = function(level, n) {
if(n.children && n.children.length > 0) {
if(levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level+1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, nodes);
newHeight = d3.max(levelWidth) * 100;
var tree = d3.tree().size([height, width])
tree.size([newHeight,height/2]);
tree.separation(function (a, b) {
return a.parent == b.parent ? 50 : 100;
});
nodes = tree(nodes);
treeLayout(nodes);
function treeLayout(nodes){
var node = diagramLayout.selectAll(".node");
node = node.data(nodes.descendants());
var link = diagramLayout.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", "1px")
.attr("stroke-opacity", "0.3")
.attr("d",connector)
nodes.descendants().slice(1).forEach(function(d) {
var mark = diagramLayout.append("svg:defs").selectAll("marker")//
.data(["start"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", -markerRefx)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto")
.attr("stroke", "#000")
.attr("fill", "#000")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("stroke-width", "0.3px")
.attr("transform","rotate(180,5, 0)");
});
link.attr("marker-start", "url(#start)")
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("height", nodeHeight)
.attr("width", nodeWidth)
nodeEnter.attr("transform", function(d) {
return "translate(" + project(d.x, d.y) + ")";
}).on('contextmenu', menuCall);
var nodeIcon = nodeEnter.append("rect")
.attr("class", "rect")
.attr("x", -20)
.attr("y", -20)
.attr("rx", 10)
.attr("width", 40)
.attr("height", 40)
.attr("stroke-width", function(d) { return Math.sqrt(2); })
.attr("stroke-opacity", "0.3")
.attr("stroke", "#000" )
.attr("fill", "blue" )
//wrap(nodeText, 8)
}
}
function connector(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
}
function project(x, y) {
return [x,y];
}
function menuCall(di,i) {
var nodeClicked = d3.select(this);
var menuitems = ["Highlight route"];
d3.selectAll('.context-menu').data([1])
.enter()
.append('div')
.attr('class', 'context-menu');
// close menu
d3.select('body').on('click.context-menu', function() {
d3.select('.context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
d3.selectAll('.context-menu')
.html('')
.append('ul')
.selectAll('li')
.data(menuitems).enter()
.append('li')
.on('click' , function(d) {
// d3.select('.context-menu').style('display', 'none');
if(d=="Highlight route"){
var id = nodeClicked.datum().data.id;
var node = diagramLayout.selectAll(".node");
var link = diagramLayout.selectAll(".link");
link.style("stroke","black").style("stroke-width", "1.5px")
var linkSelected = link.filter(function (d, i) {
console.log(d.data.id)
console.log(id)
return d.data.id === id;});
linkSelected.style("stroke", "red").style("stroke-width", "5px");
}
d3.select('.context-menu').style('display', 'none');
}).text(function(di) { return di; });
d3.select('.context-menu').style('display', 'none');
// show the context menu
d3.select('.context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
}
Looks like a bug in IE. If you inspect the DOM after you apply the highlight, IE reports that the inline style has overridden the sheet style (as it should), but the path does not update:
The only fix I can think us is to remove the sheet stroke style on class link
.link {
stroke-opacity: .6;
}
And just apply all the styles in-line.
Updated fiddle.
I make a drill down pie chart and it works well.
You can see it here.
But there is an issue:
1) click a node then it shows its children (level 1) or level 0 nodes.
2) Move mouse to another position and move back to the new node which contains its original position, the node disappears.
I think there is problem in this code (gradientPie.js)
var paths = gPie.selectAll("path").data(pieChart(currData), function(d) {return d.data.cat;});
var texts = gPie.selectAll("text").data(pieChart(currData), function(d) {return d.data.cat;});
var lines = gPie.selectAll("line").data(pieChart(currData), function(d) {return d.data.cat;});
var arcs = paths.enter().append("g").attr('class', 'slice');
arcs.append("path").attr("fill", function(d, i) { return "url(#gradient" + d.data.cat + ")"; })
.transition().duration(1000).attrTween("d", tweenIn).each("end", function(){
this._listenToEvents = true;
gradPie.transitioning = false;
})
.attr("id", function(d, i){return 'p' + i;})
.each(function(d) { this._current = d; });
arcs.append("text").attr("transform", function(d) {
var c = d3.svg.arc().outerRadius(radius * 1.4).innerRadius(radius).centroid(d);
return "translate(" + (0 + c[0]) + "," + (0 + c[1]) + ")";
})
.attr("dy", ".35em")
.attr("class", "text-main")
.style("text-anchor", "middle")
.style("fill", "#3f5763")
.style("font", "bold 14px Helvetica")
.text(function(d) {
$("#" + d.data.domID + " p").html(d.data.percent + "%");
return d.data.percent + "%";
});
arcs.append("line").attr("transform", function (d, i) {
var rAngle = ((d.startAngle + d.endAngle) / 2 - (Math.PI / 2)) * 180 / Math.PI;
return "rotate(" + rAngle + ")translate(" + radius * 1.1 + ")";
})
.attr("class", "line-ticks")
.attr('stroke-width', '1')
.attr("x2", -0.5 * radius)
.style("stroke", "#3f5763")
.style("fill", "none");
// Mouse interaction handling
paths.on("click", function(d, i){
if(this.childNodes[0]._listenToEvents && !gradPie.transitioning){
// Reset inmediatelly
d3.select(this).attr("transform", "translate(0,0)")
// Change level on click if no transition has started
paths.each(function(){
this.childNodes[0]._listenToEvents = false;
});
updateGraph(d.data.subfractions? d.data.cat : undefined);
}
})
.on("mouseover", function(d, i){
// Mouseover effect if no transition has started
if(this.childNodes[0]._listenToEvents && !gradPie.transitioning) {
// Calculate angle bisector
var ang = (d.endAngle + d.startAngle)/2;
// Transformate to SVG space
ang = (ang - (Math.PI / 2) ) * -1;
// Calculate a 10% radius displacement
var x = Math.cos(ang) * radius * 0.1;
var y = Math.sin(ang) * radius * -0.1;
d3.select(this).transition()
.duration(250).attr("transform", function() {
return "translate(" + x + ", " + y + ")";
})
}
})
.on("mouseout", function(d){
// Mouseout effect if no transition has started
if(this.childNodes[0]._listenToEvents && !gradPie.transitioning){
d3.select(this).transition()
.duration(150).attr("transform", function() {
return "translate(0,0)";
});
}
});
// Collapse sectors for the exit selection
paths.exit().transition()
.duration(1000)
.attrTween("d", tweenOut).remove();
texts.exit().transition()
.duration(100).remove();
lines.exit().transition()
.duration(100).remove();
Any help?
I'm looking for some hints as to what I am doing wrong with a Sankey diagram I'm creating. I am charting changes in food consumption over time, and using the Sankey layout to visualize how these values changed over a period of forty years.
The bl.ock and small dataset are here. The relevant code:
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 1260 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scale.category20();
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// ========================== Prepare data ==========================
queue()
.defer(d3.csv, "grains.csv")
.await(ready);
// ========================== Start viz ==========================
function ready(error, csv_data) {
nodes = [];
edges = [];
nodesArray = [];
// Scales
yearScale = d3.scale.linear().domain([1640,1688]).range([20,width -20]);
radiusScale = d3.scale.linear().domain([0,300]).range([2,12]).clamp(true);
chargeScale = d3.scale.linear().domain([0,100]).range([0,-100]).clamp(true);
uniqueValues = d3.set(nodesArray.map(function(d) {return d.name})).values();
colorScale = d3.scale.category20b(uniqueValues);
sortScale = d3.scale.ordinal().domain(uniqueValues).rangePoints([-0.001,.001]);
// Create a JSON link array
// This creates unique nodes for each item and its corresponding date.
// For example, nodes are rendered as "peas-1640," "peas-1641," etc.
csv_data.forEach(function(link) {
key = link.translation + '-' + link.date;
link.source = nodes[key] || (nodes[key] = {name: link.translation, date: link.date, origX: yearScale(parseInt(link.date)), value: link.value || 0});
});
// Build the edgesArray array
// This creates the edgesArray to correspond with unique nodes. We're telling
// items and dates to remain together. So, the code below tells the graph
// layout that `1641` is preceded by `1640` and followed by `1642`, etc.
var y = "→";
for (x in nodes) {
nodesArray.push(nodes[x])
if(nodes[y]) {
nodes[y].date = parseInt(nodes[y].date);
if (nodes[y].name == nodes[x].name) {
var newLink = {source:nodes[y], target:nodes[x]}
edges.push(newLink);
}
}
y = x;
}
sankey
.nodeWidth(10)
.nodePadding(10)
.size([1200, 1200])
.nodes(nodesArray.filter(function(d,i) {return d.date < 1650}))
.links(edges.filter(function(d,i) { return i < 50 && d.source.date < 1650 && d.target.date < 1650} )) // filtering to test a smaller data set
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(edges.filter(function(d,i) { return i < 50 && d.source.date < 1650 && d.target.date < 1650} )) // filtering to test a smaller data set
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(nodesArray.filter(function(d,i) {return d.date < 1650})) // filtering to test a smaller data set
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
};
Unfortunately, I'm getting an error as you can see in the bl.ock. The Boss suggested it might be a circular link but I'm at a bit of a loss. Any hints or suggestions?
EDIT: For some clarity, I'm after something like this:
(Source)
From what I can tell, I think I'm building the nodes and edges correctly. If we look at the console for the nodes array and edges array:
It's not like a usual Sankey or alluvial diagram, which, as I've often seen them, shows collapses and expansions of items. In my case the date, food item, and value are all a single stream throughout the length of the visualization but are resized/repositioned based on the value for a given year (like the example image above).