Prevent loop in D3js Sankey chart - javascript

I'm trying to create a sankey chart using the d3 sankey plugin with dynamic shipping data. It works great most of the time except when I get a data set like this:
[{"DeparturePort":"CHARLESTON","ArrivalPort":"BREMERHAVEN","volume":5625.74},{"DeparturePort":"CHARLESTON","ArrivalPort":"ITAPOA","volume":2340},{"DeparturePort":"PT EVERGLADES","ArrivalPort":"PT AU PRINCE","volume":41.02},{"DeparturePort":"BREMERHAVEN","ArrivalPort":"CHARLESTON","volume":28}]
The key to my issue is the first and last entry in the data set. It seems that having opposite directions in the same sankey chart sends the javascript into an infinite loop and kills the browser. Any ideas on how to prevent this from happening?
Here's my chart code where raw would be the object above:
var data = raw;
var units = "Volume";
var margin = { top: 100, right: 0, bottom: 30, left: 0 },
width = $("#"+divID).width() - margin.left - margin.right,
height = divID == "enlargeChart" ? 800 - margin.top - margin.bottom : 600 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function (d) { return ""; },
color = d3.scale.ordinal()
.range(["#0077c0", "#FF6600"]);
// append the svg canvas to the page
var svg = d3.select("#"+divID).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("font-size", "12px")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey(width)
.nodeWidth(10)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// load the data (using the timelyportfolio csv method)
//d3.csv("sankey.csv", function (error, data) {
//set up graph in same style as original example but empty
graph = { "nodes": [], "links": [] };
var checklist = [];
data.forEach(function (d) {
if ($.inArray(d.DeparturePort, checklist) == -1) {
checklist.push(d.DeparturePort)
graph.nodes.push({ "name": d.DeparturePort });
}
if ($.inArray(d.ArrivalPort, checklist) == -1) {
checklist.push(d.ArrivalPort)
graph.nodes.push({ "name": d.ArrivalPort });
}
graph.links.push({
"source": d.DeparturePort,
"target": d.ArrivalPort,
"value": +d.volume
});
});
// return only the distinct / unique nodes
graph.nodes = d3.keys(d3.nest()
.key(function (d) { return d.name; })
.map(graph.nodes));
// loop through each link replacing the text with its index from node
graph.links.forEach(function (d, i) {
graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
});
//now loop through each nodes to make nodes an array of objects
// rather than an array of strings
graph.nodes.forEach(function (d, i) {
graph.nodes[i] = { "name": d };
});
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) { return Math.max(1, d.dy); })
.sort(function (a, b) { setTimeout(function () { return b.dy - a.dy; }, 10);});
// add the link titles
link.append("title")
.text(function (d) {
return d.source.name + " → " +
d.target.name + "\n" + d.value.toFixed(0) + " TEU";
});
$("#" + divID + " .loading").addClass("hide");
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
//setTimeout(function () {
return "translate(" + d.x + "," + d.y + ")";
//}, 10);
})
.call(d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", function () {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) { if (d.dy < 0) { d.dy = (d.dy * -1); } return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name);
})
.style("stroke", function (d) {
return d3.rgb(d.color);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.style("stroke", function (d) { return "#000000" })
.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");
// the function for moving the nodes
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);
}
}, 0)

It's an issue with the sankey.js script.
See this commit (on a fork of sankey.js) which fixed it:
https://github.com/soxofaan/d3-plugin-captain-sankey/commit/0edba18918aac3e9afadffd4a169c47f88a98f81
while (remainingNodes.length) {
becomes:
while (remainingNodes.length && x < nodes.length) {
That should prevent the endless loop.

Related

Sankey diagram with D3 v7 - mouseover node highlights links

I am making a Sankey diagram with D3 v7 where I hope that on mouseover of the node all connected paths will be highlighted and the other nodes will lower in opacity.
I’ve tried to follow this example: D3.js Sankey Chart - How can I highlight the set of links coming from a node? but I am new to JS so am not sure what this part is doing
function (l) {return l.source === d || l.target === d ? 0.5 : 0.2;});
I am finding that there are many examples of this for v4 of d3 but I can’t find one that works on v7.
In addition, I would like fade out all nodes that are not connected to the selected node. Is this possible?
Any advice would be very much appreciated!
Screen shot of current layout:
Would like it to be like this on mouseover of node:
// set the dimensions and margins of the graph
var margin = { top: 10, right: 50, bottom: 10, left: 50 },
width = 1920 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
// format variables
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function (d) { return formatNumber(d); },
color = d3.scaleOrdinal().range(["#002060ff", "#164490ff", "#4d75bcff", "#98b3e6ff", "#d5e2feff", "#008cb0ff"]);
// append the svg object to the body of the page
var svg = d3.select("body").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 + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(100)
.nodePadding(40)
.size([width, height]);
var path = sankey.links();
// load the data
d3.json("sankey.json").then(function (sankeydata) {
graph = sankey(sankeydata);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return d.width; });
// add the link titles
link.append("title")
.text(function (d) {
return d.source.name + " → " +
d.target.name;
});
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
// add the rectangles for the nodes
node.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
// Attempt at getting whole length of link to highlight
.on("mouseover", function (d) {
link
.transition()
.duration(300)
.style("stroke-opacity", function (l) {
return l.source === d || l.target === d ? 0.5 : 0.2;
});
})
.on("mouseleave", function (d) {
link
.transition()
.duration(300)
.style("stroke-opacity", 0.2);
})
// Node hover titles
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.style("fill", "#3f3f3f")
.attr("x", function (d) { return d.x0 - 6; })
.attr("y", function (d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function (d) { return d.name; })
.filter(function (d) { return d.x0 < width / 2; })
.attr("x", function (d) { return d.x1 + 6; })
.attr("text-anchor", "start")
;
});

In d3.js treemap layout how to make the last child clickable and fill the entire treemap layout

Im using d3.js treemap layout . In the treemap , on clicking the parent segment with the children property it transitions into its respective child sub segments . But on clicking the last child without any children property it wont make the transition happens to fill only that child node in the entire layout . In the code that im working on I can redirect to a new window on click of the last child but not sure how to make the transition happening .
Find my code in the link https://codesandbox.io/s/affectionate-thunder-l024x
class TreemapChart extends React.Component {
componentDidMount() {
const self = this;
var margin = { top: 20, right: 0, bottom: 0, left: 0 },
width = 960,
height = 500 - margin.top - margin.bottom,
transitioning;
var x = d3.scale
.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale
.linear()
.domain([0, height])
.range([0, height]);
var treemap = d3.layout
.treemap()
.children(function(d, depth) {
return depth ? null : d._children;
})
.sort(function(a, b) {
return a.value - b.value;
})
.ratio((height / width) * 0.5 * (1 + Math.sqrt(5)))
.round(false);
var svg = d3
.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
// .on("mousemove", function (d) {
// tool.style("left", d3.event.pageX + 10 + "px")
// tool.style("top", d3.event.pageY - 20 + "px")
// tool.style("display", "inline-block");
// tool.html(d.children ? null : d.name + "<br>" );
// }).on("mouseout", function (d) {
// tool.style("display", "none");
// });
var grandparent = svg.append("g").attr("class", "grandparent");
grandparent
.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top);
grandparent
.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".35em");
function dataMap(root) {
initialize(root);
accumulate(root);
accumulateCount(root);
layout(root);
display(root);
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
}
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
function accumulate(d) {
return (d._children = d.children)
? (d.value = d.children.reduce(function(p, v) {
return p + accumulate(v);
}, 0))
: d.value;
}
function accumulateCount(d) {
return (d._children = d.children)
? (d.count = d.children.reduce(function(p, v) {
return p + accumulateCount(v);
}, 0))
: d.count;
}
function layout(d) {
if (d._children) {
treemap.nodes({ _children: d._children });
d._children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
function display(d) {
// console.log(d);
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg
.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1
.selectAll("g")
.data(d._children)
.enter()
.append("g");
g.filter(function(d) {
return d._children;
})
.classed("children", true)
.on("click", transition);
g.selectAll(".child")
.data(function(d) {
return d._children || [d];
})
.enter()
.append("rect")
.attr("class", "child")
.call(rect);
g.append("rect")
.attr("class", "parent")
.call(rect)
.on("click", function(d) {
if (!d._children) {
window.open(d.url);
}
})
.append("title")
.text(function(d) {
return `${d.name} (${d.count})`;
});
g.append("text")
.attr("dx", "1rem")
.attr("dy", "2rem")
.text(function(d) {
return `${d.name}`;
})
.call(text);
function transition(d) {
self.props.onClickSegment(d.metricsValue);
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function(a, b) {
return a.depth - b.depth;
});
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll("text")
.call(text)
.style("fill-opacity", 0);
t2.selectAll("text")
.call(text)
.style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function() {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
function text(text) {
text
.attr("x", function(d) {
return x(d.x) + 6;
})
.attr("y", function(d) {
return y(d.y) + 6;
});
}
function rect(rect) {
rect
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y);
})
.attr("width", function(d) {
return x(d.x + d.dx) - x(d.x);
})
.attr("height", function(d) {
return y(d.y + d.dy) - y(d.y);
});
}
function name(d) {
return d.parent ? name(d.parent) + " / " + d.name : d.name;
}
}
dataMap(dataObj);
}
handleClick = d => {
// this.props.onClickSegment(d.metricsValue);
console.log("The text", d.metricsValue);
};
render() {
return <p id="chart" />;
}
}
I need to click the last child and on clicking the last child should fill the layout with the respective breadcrumb like in the code.

D3 chart for visualizing data by sector and overall and algo explained [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I need to create D3 chart as in the following link:
D3 bubble chart (Data displayed as Overall and by Sector)
Below are the screenshots of charts:
Overall View:
Sector view:
I have searched a lot, but not able to find any codebase or example for the same. So, i'm not sure of How i can achieve the same.
Please share the code/pointers to achieve the same chart using D3.
Thanks in advance,
Manish Kumar
Below you can find the code for the chart in your link. It is in the source code of the website. I would suggest you learn from it, but i wouldn't copy it...
(function() {
var margin = {top: 20, right: 95, bottom: 10, left: 125},
width = 970 - margin.left - margin.right,
height,
tickExtension = 20; // extend grid lines beyond scale range
var formatPercent = d3.format(".0%"),
formatTenthPercent = d3.format(".1%"),
formatNumber = d3.format(",.3s"),
formatDollars = function(d) { return (d < 0 ? "-" : "") + "$" + formatNumber(Math.abs(d)).replace(/G$/, "B"); };
var nameAll = "S.\x26P.\xa0500 companies";
var x = d3.scale.linear()
.domain([0, .6])
.rangeRound([0, width - 60])
.clamp(true)
.nice();
var y = d3.scale.ordinal();
var y0 = d3.scale.ordinal()
.domain([nameAll])
.range([150]);
var r = d3.scale.sqrt()
.domain([0, 1e9])
.range([0, 1]);
var z = d3.scale.threshold()
.domain([.1, .2, .3, .4, .5])
.range(["#b35806", "#f1a340", "#fee0b6", "#d8daeb", "#998ec3", "#542788"].reverse());
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(5)
.tickFormat(formatPercent);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width + 60 - tickExtension * 2, 0)
.tickPadding(6);
var quadtree = d3.geom.quadtree()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
var svg = d3.select(".g-graphic").append("svg")
.attr("height", 420 + margin.top + margin.bottom)
.attr("width", width + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.select(".g-graphic").append("svg")
.style("margin-top", "20px")
.attr("height", 80)
.attr("width", width + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(renderChartKey);
var gx = svg.append("g")
.attr("class", "g-x g-axis")
.call(xAxis);
var tickLast = gx.selectAll(".g-x .tick:last-of-type");
tickLast.select("text")
.text(function() { return "\u2265 " + this.textContent; });
tickLast.select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
.attr("transform", "translate(" + width + ",0)")
.select("text")
.text("N.A.");
var titleX = gx.append("text")
.attr("class", "g-title")
.attr("y", -9)
.style("text-anchor", "end");
titleX.append("tspan")
.attr("x", -20)
.style("font-weight", "bold")
.text("Effective tax rate");
titleX.append("tspan")
.attr("x", -20)
.attr("dy", "1em")
.text("2007-12");
d3.tsv("http://graphics8.nytimes.com/newsgraphics/2013/05/13/corporate-taxes/ee84b0191a75f5c652087293ab0efd4710e21f94/companies.tsv", type, function(error, companies) {
var sectors = d3.nest()
.key(function(d) { return d.sector; })
.entries(companies);
// Compute the overall rate for all data.
var overallRate = rate(d3.sum(companies, taxes), d3.sum(companies, earnings));
// Compute the overall rate by sector.
sectors.forEach(function(d) {
d.rate = rate(d3.sum(d.values, taxes), d3.sum(d.values, earnings));
});
// Sort sectors by ascending overall rate.
sectors.sort(function(a, b) {
return a.rate - b.rate;
});
// Compute the rate for each company.
companies.forEach(function(d) {
d.rate = rate(d.taxes, d.earnings);
});
height = 120 * sectors.length;
y
.domain(sectors.map(function(d) { return d.key; }))
.rangePoints([10, height], 1);
svg.append("g")
.attr("class", "g-y g-axis g-y-axis-sector")
.attr("transform", "translate(-" + tickExtension + ",0)")
.call(yAxis.scale(y))
.call(yAxisWrap)
.style("stroke-opacity", 0)
.style("fill-opacity", 0)
.selectAll(".tick text,.tick tspan")
.attr("x", -95)
.style("text-anchor", "start");
svg.append("g")
.attr("class", "g-y g-axis g-y-axis-overall")
.attr("transform", "translate(-" + tickExtension + ",0)")
.call(yAxis.scale(y0))
.call(yAxisWrap);
var companyClip = svg.append("defs").selectAll("clipPath")
.data(companies)
.enter().append("clipPath")
.attr("id", function(d, i) { return "g-clip-company-" + i; })
.append("circle")
.attr("cx", function(d) { return d.cx; })
.attr("cy", function(d) { return d.cy - y0(nameAll); })
.attr("r", function(d) { return r(d.capitalization) + 20; });
var gVoronoi = svg.append("g")
.attr("class", "g-voronoi")
gVoronoi.selectAll("path")
.data(companies)
.enter().append("path")
.attr("clip-path", function(d, i) { return "url(#g-clip-company-" + i + ")"; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
gVoronoi.call(updateVoronoi,
function(d) { return d.cx; },
function(d) { return d.cy + y0(nameAll); },
420);
var sector = svg.append("g")
.attr("class", "g-sector")
.selectAll("g")
.data(sectors)
.enter().append("g")
.attr("transform", function(d) { return "translate(0," + y(d.key) + ")"; });
var sectorNote = d3.select(".g-sector-notes")
.style("opacity", 0)
.style("display", "none")
.selectAll("div")
.data(sectors)
.enter().append("div")
.attr("class", "g-sector-note")
.style("top", function(d) { return y(d.key) + "px"; })
.html(function(d) { return sectorNoteByName[d.key]; });
var sectorCompany = sector.append("g")
.attr("class", "g-sector-company")
.selectAll("circle")
.data(function(d) { return d.values; })
.enter().append("circle")
.attr("cx", function(d) { return d.cx; })
.attr("cy", function(d) { return d.cy - y(d.sector) + y0(nameAll); })
.attr("r", function(d) { return r(d.capitalization); })
.style("fill", function(d) { return isNaN(d.rate) ? null : z(d.rate); })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
var sectorOverall = sector.append("g")
.attr("class", "g-overall")
.attr("transform", function(d) { return "translate(" + x(d.rate) + "," + (y0(nameAll) - y(d.key)) + ")"; })
.style("stroke-opacity", 0)
.style("fill-opacity", 0);
sectorOverall.append("line")
.attr("y1", -100)
.attr("y2", +127);
var sectorOverallText = sectorOverall.append("text")
.attr("y", -106);
sectorOverallText.append("tspan")
.attr("x", 0)
.text(function(d) { return formatPercent(d.rate); });
sectorOverallText.filter(function(d, i) { return !i; }).append("tspan")
.attr("x", 0)
.attr("dy", "-11")
.style("font-size", "8px")
.text("OVERALL");
var overall = svg.append("g")
.attr("class", "g-overall g-overall-all")
.attr("transform", "translate(" + x(overallRate) + "," + y0(nameAll) + ")");
overall.append("line")
.attr("y1", -100)
.attr("y2", +127);
var overallText = overall.append("text")
.attr("y", -106)
.style("font-weight", "bold");
overallText.append("tspan")
.attr("x", 0)
.style("font-size", "13px")
.text(formatTenthPercent(overallRate));
overallText.append("tspan")
.attr("x", 0)
.attr("dy", "-14")
.style("font-size", "8px")
.text("OVERALL");
var currentView = "overall";
d3.selectAll(".g-content button[data-view]")
.datum(function(d) { return this.getAttribute("data-view"); })
.on("click", transitionView);
var searchInput = d3.select(".g-search input")
.on("keyup", keyuped);
var searchClear = d3.select(".g-search .g-search-clear")
.on("click", function() {
searchInput.property("value", "").node().blur();
search();
});
var tip = d3.select(".g-tip");
var tipMetric = tip.selectAll(".g-tip-metric")
.datum(function() { return this.getAttribute("data-name"); });
d3.selectAll(".g-annotations b,.g-sector-notes b")
.datum(function() { return new RegExp("\\b" + d3.requote(this.textContent), "i"); })
.on("mouseover", mouseoverAnnotation)
.on("mouseout", mouseout);
function keyuped() {
if (d3.event.keyCode === 27) {
this.value = "";
this.blur();
}
search(this.value.trim());
}
function search(value) {
if (value) {
var re = new RegExp("\\b" + d3.requote(value), "i");
svg.classed("g-searching", true);
sectorCompany.classed("g-match", function(d) { return re.test(d.name) || re.test(d.sector) || (d.symbol && re.test(d.symbol)) || (d.alias && re.test(d.alias)); });
var matches = d3.selectAll(".g-match");
if (matches[0].length === 1) mouseover(matches.datum());
else mouseout();
searchClear.style("display", null);
} else {
mouseout();
svg.classed("g-searching", false);
sectorCompany.classed("g-match", false);
searchClear.style("display", "none");
}
}
function transitionView(view) {
if (currentView === view) view = view === "overall" ? "sector" : "overall";
d3.selectAll(".g-buttons button[data-view]").classed("g-active", function(v) { return v === view; })
switch (currentView = view) {
case "overall": return void transitionOverall();
case "sector": return void transitionSector();
}
}
function transitionOverall() {
gVoronoi.style("display", "none");
var transition = d3.transition()
.duration(750);
transition.select("svg")
.delay(720)
.attr("height", 420 + margin.top + margin.bottom)
.each("end", function() {
gVoronoi.call(updateVoronoi,
function(d) { return d.cx; },
function(d) { return d.cy + y0(nameAll); },
420);
});
transition.select(".g-annotations-overall")
.each("start", function() { this.style.display = "block"; })
.style("opacity", 1);
transition.select(".g-sector-notes")
.style("opacity", 0)
.each("end", function() { this.style.display = "none"; });
transition.selectAll(".g-y-axis-sector")
.style("stroke-opacity", 0)
.style("fill-opacity", 0);
transition.selectAll(".g-y-axis-overall")
.style("stroke-opacity", 1)
.style("fill-opacity", 1);
var transitionOverall = transition.select(".g-overall-all")
.delay(x(overallRate))
.style("stroke-opacity", 1)
.style("fill-opacity", 1);
transitionOverall.select("line")
.attr("y2", +127);
transitionOverall.select("text")
.attr("y", -106);
var transitionSectorOverall = transition.selectAll(".g-sector .g-overall")
.delay(function(d) { return x(d.rate); })
.attr("transform", function(d) { return "translate(" + x(d.rate) + "," + (y0(nameAll) - y(d.key)) + ")"; })
.style("stroke-opacity", 0)
.style("fill-opacity", 0);
transitionSectorOverall.select("line")
.attr("y1", -100)
.attr("y2", +127);
transitionSectorOverall.select("text")
.attr("y", -106);
transition.selectAll(".g-sector-company circle")
.delay(function(d) { return d.cx; })
.attr("cx", function(d) { return d.cx; })
.attr("cy", function(d) { return d.cy - y(d.sector) + y0(nameAll); });
}
function transitionSector() {
gVoronoi.style("display", "none");
var transition = d3.transition()
.duration(750);
transition.select("svg")
.attr("height", height + margin.top + margin.bottom)
.transition()
.delay(720)
.each("end", function() {
gVoronoi.call(updateVoronoi,
function(d) { return d.x; },
function(d) { return y(d.sector) + d.y; },
height);
});
transition.select(".g-annotations-overall")
.style("opacity", 0)
.each("end", function() { this.style.display = "none"; });
transition.select(".g-sector-notes")
.delay(250)
.each("start", function() { this.style.display = "block"; })
.style("opacity", 1);
transition.selectAll(".g-y-axis-sector,.g-sector-note")
.delay(250)
.style("stroke-opacity", 1)
.style("fill-opacity", 1);
transition.selectAll(".g-y-axis-overall")
.style("stroke-opacity", 0)
.style("fill-opacity", 0);
var transitionOverall = transition.select(".g-overall-all")
.delay(x(overallRate))
.style("stroke-opacity", 0)
.style("fill-opacity", 0);
transitionOverall.select("line")
.attr("y2", height - y0(nameAll));
var transitionSectorOverall = transition.selectAll(".g-sector .g-overall")
.delay(function(d) { return x(d.rate); })
.attr("transform", function(d) { return "translate(" + x(d.rate) + ",0)"; })
.style("stroke-opacity", 1)
.style("fill-opacity", 1);
transitionSectorOverall.select("line")
.attr("y1", -25)
.attr("y2", +25);
transitionSectorOverall.select("text")
.attr("y", -31);
transition.selectAll(".g-sector-company circle")
.delay(function(d) { return d.x; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function updateVoronoi(gVoronoi, x, y, height) {
companyClip
.attr("cx", x)
.attr("cy", y);
gVoronoi
.style("display", null)
.selectAll("path")
.data(d3.geom.voronoi().x(x).y(y)(companies))
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; });
}
function mouseoverAnnotation(re) {
var matches = sectorCompany.filter(function(d) { return re.test(d.name) || re.test(d.alias); }).classed("g-active", true);
if (d3.sum(matches, function(d) { return d.length; }) === 1) mouseover(matches.datum());
else tip.style("display", "none");
}
function mouseover(d) {
sectorCompany.filter(function(c) { return c === d; }).classed("g-active", true);
var dx, dy;
if (currentView === "overall") dx = d.cx, dy = d.cy + y0(nameAll);
else dx = d.x, dy = d.y + y(d.sector);
dy -= 19, dx += 50; // margin fudge factors
tip.style("display", null)
.style("top", (dy - r(d.capitalization)) + "px")
.style("left", dx + "px");
tip.select(".g-tip-title")
.text(d.alias || d.name);
tipMetric.select(".g-tip-metric-value").text(function(name) {
switch (name) {
case "rate": return isNaN(d.rate) ? "N.A." : formatPercent(d.rate);
case "taxes": return formatDollars(d.taxes);
case "earnings": return formatDollars(d.earnings);
}
});
}
function mouseout() {
tip.style("display", "none");
sectorCompany.filter(".g-active").classed("g-active", false);
}
});
function renderChartKey(g) {
var formatPercent = d3.format(".0%"),
formatNumber = d3.format(".0f");
// A position encoding for the key only.
var x = d3.scale.linear()
.domain([0, .6])
.range([0, 240]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(13)
.tickValues(z.domain())
.tickFormat(function(d) { return d === .5 ? formatPercent(d) : formatNumber(100 * d); });
g.append("text")
.attr("x", -25)
.style("text-anchor", "end")
.style("font", "bold 9px sans-serif")
.text("CHART KEY");
var gColor = g.append("g")
.attr("class", "g-key-color")
.attr("transform", "translate(140,-7)");
gColor.selectAll("rect")
.data(z.range().map(function(d, i) {
return {
x0: i ? x(z.domain()[i - 1]) : x.range()[0],
x1: i < 4 ? x(z.domain()[i]) : x.range()[1],
z: d
};
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return d.x0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.style("fill", function(d) { return d.z; });
gColor.call(xAxis);
var gColorText = g.append("text")
.attr("x", 140 - 6)
.style("text-anchor", "end");
gColorText.append("tspan")
.style("font-weight", "bold")
.text("Color");
gColorText.append("tspan")
.style("fill", "#777")
.text(" shows effective rate");
var gSize = g.append("g")
.attr("class", "g-key-size")
.attr("transform", "translate(580,-7)");
var gSizeInstance = gSize.selectAll("g")
.data([1e9, 10e9, 50e9, 100e9])
.enter().append("g")
.attr("class", "g-sector");
gSizeInstance.append("circle")
.attr("r", r);
gSizeInstance.append("text")
.attr("x", function(d) { return r(d) + 4; })
.attr("dy", ".35em")
.text(function(d) { return "$" + Math.round(d / 1e9) + "B"; });
var gSizeX = 0;
gSizeInstance.attr("transform", function() {
var t = "translate(" + gSizeX + ",3)";
gSizeX += this.getBBox().width + 15;
return t;
});
var gSizeText = g.append("text")
.attr("x", 580 - 10)
.style("text-anchor", "end");
gSizeText.append("tspan")
.style("font-weight", "bold")
.text("Size");
gSizeText.append("tspan")
.style("fill", "#777")
.text(" shows market capitalization");
}
function yAxisWrap(g) {
g.selectAll(".tick text")
.filter(function(d) { return /[ ]/.test(d) && this.getComputedTextLength() > margin.left - tickExtension - 10; })
.attr("dy", null)
.each(function(d) {
d3.select(this).text(null).selectAll("tspan")
.data(d.split(" "))
.enter().append("tspan")
.attr("x", this.getAttribute("x"))
.attr("dy", function(d, i) { return (i * 1.35 - .35) + "em"; })
.text(function(d) { return d; });
});
}
function taxes(d) {
return d.taxes;
}
function earnings(d) {
return d.earnings;
}
function rate(taxes, earnings) {
return earnings <= 0 ? NaN : taxes / earnings;
}
function type(d) {
d.x = +d.x;
d.y = +d.y;
d.cx = +d.cx;
d.cy = +d.cy;
d.taxes *= 1e6;
d.earnings *= 1e6;
d.capitalization *= 1e6;
return d;
}
var sectorNoteByName = {
"Utilities": "Utilities benefited from the 2009 stimulus bill, which included tax breaks for companies that make capital-intensive investments, like power plants.",
"Information technology": "Technology companies can often move operations overseas for accounting purposes. And younger firms tend to have recent losses, holding down the sector’s overall rate.",
"Industrials": "As with the corporate sector, large industrial companies — like <b>Boeing</b>, <b>Caterpillar</b>, <b>General Electric</b> and <b>Honeywell</b> — pay lower taxes on average than small companies.",
"Telecom": "<b>Verizon</b> had a much lower effective tax rate than its rival <b>AT&T</b>, despite having similar profits over the six-year period.",
"Health care": "Within health care, managed care companies pay relatively higher tax rates, and makers of equipment, supplies and technology pay relatively lower rates.",
"Pharma": "Tax breaks for research and the ability to locate operations in low-tax countries have helped pharmaceutical and biotech companies to pay low taxes.",
"Consumer products": "Movie studios and packaged-food company pay more than 30 percent, on average. Soft-drink companies pay only 19 percent, and restaurant companies, 25 percent.",
"Materials": "The materials industry (chemicals, minerals, etc.) exemplifies a point often made by tax experts: within industries, tax rates vary greatly, in ways that often evade simple explanation.",
"Financials": "As financial firms have recovered from the crisis, some have paid relatively high tax rates.",
"Retailers": "Brick-and-mortar retailers, like <b>Bed Bath & Beyond</b> and <b>Home Depot</b>, tend to pay high tax rates. Online retailers, like <b>Amazon</b>, face low rates.",
"Energy": "Large oil companies typically pay high rates, but some economists argue that the high rates do not cover the pollution costs imposed on society.",
"Insurance": "Many insurers pay lower-than-average rates. But <b>A.I.G.</b> — which had an $83 billion loss while paying $8 billion in taxes — drives the sector’s average up."
};
})()

How to make nodes in sankey diagram clickable using d3.js library

Code Snippet :
We are using d3.js for this.
Sankey diagrams is made up of nodes and links.
Here the data comes from json file.
So how to make all the nodes clickable.
Which methods can we use with the rectangles so that we can make the nodes clickable.
<script>
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), //decimal places
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();
//d3.json("energy.json", function(energy) {
d3.json("numbers-subset.json", function(energy) {
sankey
.nodes(energy.nodes)
.links(energy.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.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(energy.nodes)
.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.split("|")[0]); })
.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);
}
});
</script>
D3.js is very powerful library give you control over each event to each pixel.
but we need to do one thing in the case of events.
Overlapping event we need to by forget .
Add following code in code.
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.on("click",function(d){
if (d3.event.defaultPrevented) return;
alert("clicked!"+d.value);
})
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));

How to force y position of one branch in d3 sankey plugin?

I would like to force one branch of sankey diagram to be on top.
Instead of diagram like this:
would like to generate diagram where nodes 1, 2, 7, 15, 10 and 14 are always on top:
Link to fiddle with current code: http://jsfiddle.net/w5jfp9t0/1/
var margin = {top: 1, right: 1, bottom: 6, left: 1};
var width = 1052 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d); },
color = d3.scale.category20();
var svg = d3.select("#chart_sankey").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(35)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
raw = '{"nodes":[{"name":"Node 1"},{"name":"Node 2"},{"name":"Node 3"},{"name":"Node 4"},{"name":"Node 5"},{"name":"Node 6"},{"name":"Node 7"},{"name":"Node 8"},{"name":"Node 9"},{"name":"Node 10"},{"name":"Node 11"},{"name":"Node 12"},{"name":"Node 13"},{"name":"Node 14"},{"name":"Node 15"}],"links":[{"source":9,"target":13,"value":25},{"source":14,"target":9,"value":37},{"source":14,"target":11,"value":16},{"source":14,"target":12,"value":8},{"source":14,"target":10,"value":68},{"source":6,"target":14,"value":154},{"source":6,"target":8,"value":40},{"source":1,"target":6,"value":345},{"source":1,"target":7,"value":66},{"source":1,"target":3,"value":17},{"source":1,"target":4,"value":25},{"source":1,"target":5,"value":117},{"source":0,"target":1,"value":692},{"source":0,"target":2,"value":19}]}';
data = JSON.parse(raw);
sankey.nodes(data.nodes)
.links(data.links)
.layout(32);
var link = svg.append("g")
.selectAll(".link")
.data(data.links)
.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; });
var nodes = data.nodes;
var node = svg.append("g").selectAll(".node")
.data(nodes)
.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));
sankey.relayout();
node.filter(function(d) { return d.value != 0; }) // append text only if node value is not zero
.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.filter(function(d) { return d.value != 0; }) // append text only if node value is not zero
.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 == 0; }) // at first column append text after column
.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);
}
What do I need to change to accomplish this?
Note
To be honest, I never used that plugin. I don't see an option to get the desired behaviour directly - thus I looked at the source of sankey.js to make the adjustments. Below I show how I'd modify - you might want to do it more thoroughly :)
Idea
Looking at the code of sankey.js, you see that the nodes are placed (y-direction) using the center function:
function center(node) {
return node.y + node.dy / 2;
}
As I don't see a parameter to change that behaviour, you can change it to:
function center(node) {
return 0;
}
If you then also revert the sorting order:
function ascendingDepth(a, b) {
return b.y - a.y;
}
you get the following picture:

Categories