forked from EE2dev's block: sequence explorer - first example
Created
January 18, 2017 01:32
-
-
Save Thanaporn-sk/9820027484504c3564ecff1c360310d1 to your computer and use it in GitHub Desktop.
sequence explorer - first example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> <!-- also save this file as unicode-8 ! --> | |
<head> | |
<link rel="stylesheet" type="text/css" href="sankeySeqExplorer.css"> | |
<!--script src="../../../lib/d3_v4_2_1/d3.js"></script--> | |
<script src="http://d3js.org/d3.v4.js"></script> | |
<script src="sankeySeqExplorer.js"></script> | |
<script src="sankeySeq.js"></script> | |
</head> | |
<body> | |
<!-- A: paste data in pre#data tag --> | |
<pre id="data"> | |
value,sourceX,sourceY,targetX,targetY | |
100,2000,1,2001,0,100 | |
100,2000,1,2001,1,100 | |
10,2000,1,2001,2,10 | |
10,2000,1,2001,3+,10 | |
100,2000,2,2001,2,100 | |
120,2000,2,2001,0,120 | |
100,2001,1,2002,1,100 | |
10,2002,1,2003,2,10 | |
90,2002,1,2003,1,90 | |
</pre> | |
<!-- A: paste node infos in pre#dataNodes tag --> | |
<pre id="dataNodes"></pre> | |
<script> | |
// no parameter when data is embedded in <pre id="data"> tag, otherwise reUsableChart(file); | |
// var myChart = reUsableChart() | |
var myChart = reUsableChart("sankey_new_n2.csv") | |
.nodeWidth(15) | |
.nodePadding(10) | |
.debugOn(false) | |
.size([800, 600]) | |
// .margin(20) | |
//.sequence(["2003","2002","2001","2000"]) | |
//.categories(["3+", "2", "1", "0"]) | |
.sequenceName("year") | |
.categoryName("mood") | |
; | |
d3.select("body") | |
.append("div") | |
.attr("class", "chart") | |
.call(myChart); | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
value | sourceX | sourceY | targetX | targetY | first_product | age | |
---|---|---|---|---|---|---|---|
100 | 2000 | 1 | 2001 | 0 | A | 18-30 | |
100 | 2000 | 1 | 2001 | 1 | A | 18-30 | |
10 | 2000 | 1 | 2001 | 2 | A | 18-30 | |
10 | 2000 | 1 | 2001 | 3+ | A | 18-30 | |
100 | 2000 | 2 | 2001 | 2 | A | 18-30 | |
120 | 2000 | 2 | 2001 | 0 | A | 18-30 | |
100 | 2001 | 1 | 2002 | 1 | A | 18-30 | |
10 | 2002 | 1 | 2003 | 2 | A | 18-30 | |
90 | 2002 | 1 | 2003 | 1 | A | 18-30 | |
100 | 2000 | 1 | 2001 | 0 | A | 30-50 | |
100 | 2000 | 1 | 2001 | 1 | A | 30-50 | |
10 | 2000 | 1 | 2001 | 2 | A | 30-50 | |
10 | 2000 | 1 | 2001 | 3+ | A | 30-50 | |
100 | 2000 | 2 | 2001 | 2 | A | 30-50 | |
120 | 2000 | 2 | 2001 | 0 | A | 30-50 | |
100 | 2001 | 1 | 2002 | 1 | A | 30-50 | |
10 | 2002 | 1 | 2003 | 2 | A | 30-50 | |
90 | 2002 | 1 | 2003 | 1 | A | 30-50 | |
100 | 2000 | 1 | 2001 | 0 | A | 50+ | |
100 | 2000 | 1 | 2001 | 1 | A | 50+ | |
10 | 2000 | 1 | 2001 | 2 | A | 50+ | |
10 | 2000 | 1 | 2001 | 3+ | A | 50+ | |
100 | 2000 | 2 | 2001 | 2 | A | 50+ | |
120 | 2000 | 2 | 2001 | 0 | A | 50+ | |
100 | 2001 | 1 | 2002 | 1 | A | 50+ | |
10 | 2002 | 1 | 2003 | 2 | A | 50+ | |
90 | 2002 | 1 | 2003 | 1 | A | 50+ | |
100 | 2000 | 1 | 2001 | 0 | B | 18-30 | |
100 | 2000 | 1 | 2001 | 1 | B | 18-30 | |
10 | 2000 | 1 | 2001 | 2 | B | 18-30 | |
10 | 2000 | 1 | 2001 | 3+ | B | 18-30 | |
100 | 2000 | 2 | 2001 | 2 | B | 18-30 | |
120 | 2000 | 2 | 2001 | 0 | B | 18-30 | |
100 | 2001 | 1 | 2002 | 1 | B | 18-30 | |
10 | 2002 | 1 | 2003 | 2 | B | 18-30 | |
90 | 2002 | 1 | 2003 | 1 | B | 18-30 | |
100 | 2000 | 1 | 2001 | 0 | B | 30-50 | |
100 | 2000 | 1 | 2001 | 1 | B | 30-50 | |
10 | 2000 | 1 | 2001 | 2 | B | 30-50 | |
10 | 2000 | 1 | 2001 | 3+ | B | 30-50 | |
100 | 2000 | 2 | 2001 | 2 | B | 30-50 | |
120 | 2000 | 2 | 2001 | 0 | B | 30-50 | |
100 | 2001 | 1 | 2002 | 1 | B | 30-50 | |
10 | 2002 | 1 | 2003 | 2 | B | 30-50 | |
90 | 2002 | 1 | 2003 | 1 | B | 30-50 | |
100 | 2000 | 1 | 2001 | 0 | B | 50+ | |
100 | 2000 | 1 | 2001 | 1 | B | 50+ | |
10 | 2000 | 1 | 2001 | 2 | B | 50+ | |
10 | 2000 | 1 | 2001 | 3+ | B | 50+ | |
100 | 2000 | 2 | 2001 | 2 | B | 50+ | |
700 | 2000 | 2 | 2001 | 0 | B | 50+ | |
100 | 2001 | 1 | 2002 | 1 | B | 50+ | |
10 | 2002 | 1 | 2003 | 2 | B | 50+ | |
90 | 2002 | 1 | 2003 | 1 | B | 50+ | |
100 | 2000 | 1 | 2001 | 0 | C | 18-30 | |
100 | 2000 | 1 | 2001 | 1 | C | 18-30 | |
10 | 2000 | 1 | 2001 | 2 | C | 18-30 | |
10 | 2000 | 1 | 2001 | 3+ | C | 18-30 | |
100 | 2000 | 2 | 2001 | 2 | C | 18-30 | |
120 | 2000 | 2 | 2001 | 0 | C | 18-30 | |
100 | 2001 | 1 | 2002 | 1 | C | 18-30 | |
10 | 2002 | 1 | 2003 | 2 | C | 18-30 | |
90 | 2002 | 1 | 2003 | 1 | C | 18-30 | |
100 | 2000 | 1 | 2001 | 0 | C | 30-50 | |
100 | 2000 | 1 | 2001 | 1 | C | 30-50 | |
10 | 2000 | 1 | 2001 | 2 | C | 30-50 | |
10 | 2000 | 1 | 2001 | 3+ | C | 30-50 | |
100 | 2000 | 2 | 2001 | 2 | C | 30-50 | |
120 | 2000 | 2 | 2001 | 0 | C | 30-50 | |
100 | 2001 | 1 | 2002 | 1 | C | 30-50 | |
10 | 2002 | 1 | 2003 | 2 | C | 30-50 | |
90 | 2002 | 1 | 2003 | 1 | C | 30-50 | |
100 | 2000 | 1 | 2001 | 0 | C | 50+ | |
100 | 2000 | 1 | 2001 | 1 | C | 50+ | |
10 | 2000 | 1 | 2001 | 2 | C | 50+ | |
10 | 2000 | 1 | 2001 | 3+ | C | 50+ | |
100 | 2000 | 2 | 2001 | 2 | C | 50+ | |
120 | 2000 | 2 | 2001 | 0 | C | 50+ | |
100 | 2001 | 1 | 2002 | 1 | C | 50+ | |
10 | 2002 | 1 | 2003 | 2 | C | 50+ | |
90 | 2002 | 1 | 2003 | 1 | C | 50+ | |
100 | 2000 | 1 | 2001 | 0 | D | 18-30 | |
100 | 2000 | 1 | 2001 | 1 | D | 18-30 | |
10 | 2000 | 1 | 2001 | 2 | D | 18-30 | |
10 | 2000 | 1 | 2001 | 3+ | D | 18-30 | |
100 | 2000 | 2 | 2001 | 2 | D | 18-30 | |
120 | 2000 | 2 | 2001 | 0 | D | 18-30 | |
100 | 2001 | 1 | 2002 | 1 | D | 18-30 | |
10 | 2002 | 1 | 2003 | 2 | D | 18-30 | |
90 | 2002 | 1 | 2003 | 1 | D | 18-30 | |
100 | 2000 | 1 | 2001 | 0 | D | 30-50 | |
100 | 2000 | 1 | 2001 | 1 | D | 30-50 | |
10 | 2000 | 1 | 2001 | 2 | D | 30-50 | |
10 | 2000 | 1 | 2001 | 3+ | D | 30-50 | |
100 | 2000 | 2 | 2001 | 2 | D | 30-50 | |
120 | 2000 | 2 | 2001 | 0 | D | 30-50 | |
100 | 2001 | 1 | 2002 | 1 | D | 30-50 | |
10 | 2002 | 1 | 2003 | 2 | D | 30-50 | |
90 | 2002 | 1 | 2003 | 1 | D | 30-50 | |
100 | 2000 | 1 | 2001 | 0 | D | 50+ | |
100 | 2000 | 1 | 2001 | 1 | D | 50+ | |
10 | 2000 | 1 | 2001 | 2 | D | 50+ | |
10 | 2000 | 1 | 2001 | 3+ | D | 50+ | |
100 | 2000 | 2 | 2001 | 2 | D | 50+ | |
120 | 2000 | 2 | 2001 | 0 | D | 50+ | |
100 | 2001 | 1 | 2002 | 1 | D | 50+ | |
10 | 2002 | 1 | 2003 | 2 | D | 50+ | |
90 | 2002 | 1 | 2003 | 1 | D | 50+ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sourceX | sourceY | first_product | age | info1 | info2 | info3 | |
---|---|---|---|---|---|---|---|
2000 | 1 | A | 18-30 | 10 | 10 | 200 | |
2000 | 2 | A | 18-30 | 60 | 60 | 40 | |
2001 | 0 | A | 18-30 | 40 | 200 | 40 | |
2000 | 1 | C | 18-30 | 80 | 60 | 40 | |
2000 | 2 | C | 18-30 | 60 | 60 | 40 | |
2001 | 0 | C | 18-30 | 40 | 60 | 40 | |
2000 | 1 | D | 50+ | 80 | 60 | 40 | |
2000 | 2 | D | 50+ | 60 | 60 | 40 | |
2001 | 0 | D | 50+ | 40 | 60 | 40 | |
2003 | 1 | D | 50+ | 80 | 10 | 40 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// extension of sankey based on Andrey's reply to http://stackoverflow.com/questions/21539265/d3-sankey-charts-manually-position-node-along-x-axis | |
d3.sankeySeq = function() { | |
var sankey = {}, | |
debugOn = false, | |
nodeWidth = 15, | |
nodePadding = 8, | |
size = [700, 500], | |
sequence = [], | |
categories = [], | |
ky, // scaling factor for height of node | |
maxValue, // the max of all node values | |
maxValueSpecified = false, // true if maxValue is specified through API | |
nodes = [], | |
links = [], | |
xScale, | |
yScale; | |
sankey.debugOn = function(_) { | |
if (!arguments.length) return debugOn; | |
debugOn = _; | |
return sankey; | |
}; | |
sankey.nodeWidth = function(_) { | |
if (!arguments.length) return nodeWidth; | |
nodeWidth = +_; | |
return sankey; | |
}; | |
sankey.nodePadding = function(_) { | |
if (!arguments.length) return nodePadding; | |
nodePadding = +_; | |
return sankey; | |
}; | |
sankey.nodes = function(_) { | |
if (!arguments.length) return nodes; | |
nodes = _; | |
return sankey; | |
}; | |
sankey.links = function(_) { | |
if (!arguments.length) return links; | |
links = _; | |
return sankey; | |
}; | |
sankey.sequence = function(_) { | |
if (!arguments.length) return sequence; | |
sequence = _; | |
return sankey; | |
}; | |
sankey.categories = function(_) { | |
if (!arguments.length) return categories; | |
categories = _; | |
return sankey; | |
}; | |
sankey.size = function(_) { | |
if (!arguments.length) return size; | |
size = _; | |
return sankey; | |
}; | |
sankey.xScale = function(_) { | |
if (!arguments.length) return xScale; | |
xScale = _; | |
return sankey; | |
}; | |
sankey.yScale = function(_) { | |
if (!arguments.length) return yScale; | |
yScale = _; | |
return sankey; | |
}; | |
sankey.relayout = function() { | |
computeLinkDepths(); | |
return sankey; | |
}; | |
sankey.link = function() { | |
var curvature = .5; | |
function link(d) { | |
var x0 = d.source.x + d.source.dx, | |
x1 = d.target.x, | |
xi = d3.interpolateNumber(x0, x1), | |
x2 = xi(curvature), | |
x3 = xi(1 - curvature), | |
y0 = d.source.y + d.sy + d.dy / 2, | |
y1 = d.target.y + d.ty + d.dy / 2; | |
return "M" + x0 + "," + y0 | |
+ "C" + x2 + "," + y0 | |
+ " " + x3 + "," + y1 | |
+ " " + x1 + "," + y1; | |
} | |
link.curvature = function(_) { | |
if (!arguments.length) return curvature; | |
curvature = +_; | |
return link; | |
}; | |
return link; | |
}; | |
sankey.layout = function() { | |
computeNodeLinks(); | |
computeNodeValues(); | |
computeNodeSizes(); // new | |
computeNodePositions(); // new | |
computeLinkDepths(); | |
return sankey; | |
}; | |
sankey.getNodeHeight = function(value) { | |
return value * ky; | |
}; | |
sankey.maxValue = function(value) { | |
if (!arguments.length) return maxValue; | |
if (value === -1) { maxValueSpecified = false; return;} | |
maxValue = value; | |
maxValueSpecified = true; | |
return sankey; | |
}; | |
// end of API functions | |
// Populate the sourceLinks and targetLinks for each node. | |
// Also, if the source and target are not objects, assume they are indices. | |
function computeNodeLinks() { | |
nodes.forEach(function(node) { | |
node.sourceLinks = []; | |
node.targetLinks = []; | |
}); | |
links.forEach(function(link) { | |
var source = link.source, | |
target = link.target; | |
if (typeof source === "number") source = link.source = nodes[link.source]; | |
if (typeof target === "number") target = link.target = nodes[link.target]; | |
source.sourceLinks.push(link); | |
target.targetLinks.push(link); | |
}); | |
} | |
// Compute the value (size) of each node by summing the associated links. | |
function computeNodeValues() { | |
nodes.forEach(function(node) { | |
node.value = Math.max( | |
d3.sum(node.sourceLinks, value), | |
d3.sum(node.targetLinks, value) | |
); | |
}); | |
} | |
// Compute the y extend of nodes and links | |
function computeNodeSizes() { | |
maxValue = maxValueSpecified ? maxValue : d3.max(nodes, function (d) { return d.value;}); | |
// calculate scaling factor ky based on #categories, height, nodePAdding and maxNodeValue | |
ky = ((size[1] / categories.length) - nodePadding) | |
/ maxValue; | |
if (debugOn) {console.log("ky: " + ky);} | |
nodes.forEach(function(node, i) { | |
node.dy = node.value * ky; | |
node.dx = nodeWidth; | |
}); | |
links.forEach(function(link) { | |
link.dy = link.value * ky; | |
}); | |
} | |
function computeNodePositions() { | |
if (debugOn) {console.log(size);} | |
xScale = d3.scalePoint().domain(sequence).range([0, size[0] - nodeWidth]); | |
yScale = d3.scalePoint().domain(categories).range([size[1], (size[1] / categories.length)]); | |
nodes.forEach(function(element) { | |
element.x = xScale(element.nameX); | |
element.y = yScale(element.nameY) - element.dy; | |
if (debugOn) { | |
console.log("x " + element.nameX + " -> " + xScale(element.nameX)); | |
console.log("y " + element.nameY + " -> " + xScale(element.nameY)); | |
} | |
}); | |
if (debugOn) {console.log(nodes);} | |
}; | |
function computeLinkDepths() { | |
nodes.forEach(function(node) { | |
node.sourceLinks.sort(ascendingTargetDepth); | |
node.targetLinks.sort(ascendingSourceDepth); | |
}); | |
nodes.forEach(function(node) { | |
var sy = 0, ty = 0; | |
node.sourceLinks.forEach(function(link) { | |
link.sy = sy; | |
sy += link.dy; | |
}); | |
node.targetLinks.forEach(function(link) { | |
link.ty = ty; | |
ty += link.dy; | |
}); | |
}); | |
function ascendingSourceDepth(a, b) { | |
return a.source.y - b.source.y; | |
} | |
function ascendingTargetDepth(a, b) { | |
return a.target.y - b.target.y; | |
} | |
} | |
function value(link) { | |
return link.value; | |
} | |
return sankey; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.unselect { | |
-webkit-touch-callout: none; | |
-webkit-user-select: none; | |
-khtml-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
user-select: none; | |
} | |
pre#data, pre#dataNodes { | |
display:none; | |
} | |
div.chart { | |
margin: 0 auto; | |
display: table; | |
} | |
div.sankeyMenu { | |
font-family: sans-serif; | |
font-size: 75%; | |
display: table-cell; | |
min-width: 100px; | |
padding-top: 18px; | |
vertical-align: top; | |
} | |
div.NodeInfoMenu, div.OptionsMenu { | |
border-style: solid; | |
border-width: 2px; | |
background-color: rgba(0,0,0,0.1); | |
border-color: rgba(0,0,0,0.1); | |
border-radius: 3px; | |
padding-right: 4px; | |
padding-top: 2px; | |
} | |
div.NodeInfoMenu { | |
margin-top: 5px; | |
} | |
.titleMenu { | |
padding: 0px; | |
font-size: 120%; | |
font-weight: bold; | |
text-align: center; | |
} | |
div.sankeyChart { | |
display: table-cell; | |
} | |
.sankeyNode { | |
fill: #023858; | |
fill-opacity: .75; | |
shape-rendering: crispEdges; | |
} | |
.node rect.sankeyNodeInfo { | |
shape-rendering: crispEdges; | |
} | |
.node text { | |
pointer-events: none; | |
text-shadow: 0 1px 0 #fff; | |
} | |
.link { | |
fill: none; | |
stroke: #023858; | |
stroke-opacity: .2; | |
} | |
.link:hover { | |
stroke-opacity: .5; | |
} | |
div.tooltip { | |
position: absolute; | |
z-index: 10; | |
visibility: hidden; | |
color: white; | |
padding: 8px; | |
background-color: rgba(0,0,0,0.75); | |
border-radius: 6px; | |
font: 12px sans-serif; | |
} | |
/* title top and left for multiples */ | |
text.multiples { | |
text-anchor: middle; | |
font-size: 12px; | |
} | |
/* title top for single */ | |
text.single { | |
text-anchor: middle; | |
font-size: 18px; | |
} | |
/* examples for hightlighting individual nodes and links | |
* | |
* selector for nodes: ".n" + "sourceX" + "-" + "sourceY" | |
* selector for links: ".l" + "<sourceX>" + "_" + "<sourceY>" + "-" + "<targetX>" + "_" + "<targetY>" | |
*/ | |
/* | |
.l2000_1-2001_1, .l2001_1-2002_1, .l2002_1-2003_2 { | |
stroke: red; | |
} | |
.n2000_2 { | |
fill: yellow; | |
} | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var reUsableChart = function(_myData) { | |
"use strict"; | |
// 0.1 All options not accessible to caller | |
var file; // reference to data (embedded or in file) | |
var nodeFile; // optional file with additional node infos | |
var nodeInfoKeys; // the key names of the additional node infos | |
var nodeInfoNone = "(none)"; // displayed string for no info key | |
var nodeInfoKey = nodeInfoNone; // the selected key | |
var valueName; // the column name of the frequency value | |
var scaleGlobal = true; // scale the node height for multiples over all sankeys | |
var showNodeLabels = "on"; // show node labels | |
var allGraphs; // data structure containing columns of rows of sankey input data; | |
/////////////////////////////////////////////////// | |
// 1.0 add visualization specific variables here // | |
/////////////////////////////////////////////////// | |
// 1.1 All options that should be accessible to caller | |
var debugOn = false, | |
nodeWidth = 15, | |
nodePadding = 8, | |
size = [700, 500], | |
margin = {left: 5, top: 0, right: 0, bottom: 0}, | |
sequence, | |
categories, | |
sequenceName = "sequence", | |
categoryName = "category"; | |
// 1.2 all updatable functions to be called by getter-setter methods | |
var updateNodeInfo; | |
//////////////////////////////////////////////////// | |
// 2.0 API for external access // | |
//////////////////////////////////////////////////// | |
// standard API for selection.call(my_reUsableChart) | |
function chartAPI(selection) { | |
selection.each( function (d) { | |
console.log(d); | |
console.log("_myData "+ _myData); | |
if (typeof d !== 'undefined') { // data processing from outside | |
createChart(selection, d); | |
} | |
else { // data processing here | |
if (typeof _myData !== 'undefined') { | |
readData(_myData, selection); | |
} | |
else { | |
readData("<pre>", selection); | |
} | |
} | |
}); | |
} | |
// API - example for getter-setter method | |
// 2.1 add getter-setter methods here | |
chartAPI.debugOn = function(_) { | |
if (!arguments.length) return debugOn; | |
debugOn = _; | |
return chartAPI; | |
}; | |
chartAPI.size = function(_) { | |
if (!arguments.length) return size; | |
size = _; | |
return chartAPI; | |
}; | |
chartAPI.margin = function(_) { | |
if (!arguments.length) return margin; | |
margin.left = _; | |
margin.top = _; | |
margin.right = _; | |
margin.bottom = _; | |
return chartAPI; | |
}; | |
chartAPI.nodeWidth = function(_) { | |
if (!arguments.length) return nodeWidth; | |
nodeWidth = +_; | |
return chartAPI; | |
}; | |
chartAPI.nodePadding = function(_) { | |
if (!arguments.length) return nodePadding; | |
nodePadding = +_; | |
return chartAPI; | |
}; | |
chartAPI.sequence = function(_) { | |
if (!arguments.length) return sequence; | |
sequence = _; | |
return chartAPI; | |
}; | |
chartAPI.categories = function(_) { | |
if (!arguments.length) return categories; | |
categories = _; | |
return chartAPI; | |
}; | |
chartAPI.sequenceName = function(_) { | |
if (!arguments.length) return sequenceName; | |
sequenceName = _; | |
return chartAPI; | |
}; | |
chartAPI.categoryName = function(_) { | |
if (!arguments.length) return categoryName; | |
categoryName = _; | |
return chartAPI; | |
}; | |
chartAPI.valueName = function(_) { | |
if (!arguments.length) return valueName; | |
valueName = _; | |
return chartAPI; | |
}; | |
//////////////////////////////////// | |
// 3.0 add private functions here // | |
//////////////////////////////////// | |
// draws axes and returns an Object with properties | |
// paddingSingle, paddingMultiples, width, height | |
// for further processing in function createChart() | |
function initialize_whp_and_axes(svg, width, height) { | |
width = size[0] - margin.left - margin.right; | |
height = size[1] - margin.bottom - margin.top; | |
var paddingSingle = {left: 0, bottom: 0, right: 0, top: 20}; | |
var paddingMultiples = {left: 20, bottom: 0, right: 0, top: 20}; // fixed | |
// drawing axes - step 1: determine size of sankey | |
var axisL = svg.append("g") | |
.attr("class", "dummy") | |
.style("opacity", 0) | |
.call(d3.axisLeft(d3.scalePoint().domain(categories))) | |
var axisB = axisL.call(d3.axisBottom(d3.scalePoint().domain(sequence))); | |
paddingSingle.left = axisL.node().getBBox().width; | |
paddingSingle.bottom = axisB.node().getBBox().height; | |
// update width and height for sankeyG | |
width = width - paddingSingle.left; | |
height = height - paddingSingle.bottom - paddingMultiples.top;; | |
d3.selectAll("g.dummy").remove(); | |
var yScale = d3.scalePoint() | |
.domain(categories) | |
.range([height, (height / categories.length)]); | |
var axisLeft = d3.axisLeft(yScale); | |
var axisSelection = svg.append("g") | |
.attr("class", "axis left") | |
.style("opacity", 0) | |
.call(axisLeft); | |
// drawing axes - step 2: calculate paddingSingle.Left | |
paddingSingle.left = axisSelection.node().getBBox().width; | |
axisSelection.attr("transform", "translate(" + (paddingSingle.left - 1.5) + ", " + paddingSingle.top + ")"); | |
var xScale = d3.scalePoint() | |
.domain(sequence) | |
.range([0, width - nodeWidth]); | |
var axisBottom = d3.axisBottom(xScale); | |
var axisSelection = svg.append("g") | |
.attr("class", "axis bottom") | |
.style("opacity", 0) | |
.call(axisBottom); | |
// drawing axes: step 3: calculate paddingSingle.bottom | |
paddingSingle.bottom = axisSelection.node().getBBox().height; | |
axisSelection.attr("transform", "translate(" + paddingSingle.left + ", " + (height + paddingMultiples.top) + ")"); | |
// move text of axisBottom: | |
// d3.select("g.axis.bottom").selectAll("text").attr("text-anchor", "start"); | |
var result = {}; | |
result.paddingSingle = paddingSingle; | |
result.paddingMultiples = paddingMultiples; | |
result.width = width; | |
result.height = height; | |
return result; | |
} | |
// draws titles, axes around the sankeyGraph | |
// for single graph and small multiples | |
function initializeFrame(selection, props, allGraphs, colIndex, rowIndex) { | |
var width = props.width; | |
var height = props.height; | |
var padding = props.paddingMultiples; | |
var tx = colIndex * width / allGraphs.cols; | |
var ty = rowIndex * height / allGraphs.rows; | |
var sx = (1 / allGraphs.cols * width - padding.left) / width; | |
var sy = (1 / allGraphs.rows * height - padding.top) / height; | |
if (sx >= sy) { sx = sy;} else { sy = sx;} // make multiples proportional to full sized chart | |
var transformString = {}; | |
transformString.sankeyFrame = {}; | |
transformString.sankeyFrame.single = "translate(" + (tx + width/2*sx) + ", " + (ty + height/2*sy) + ") scale(0.05)"; | |
transformString.sankeyFrame.multiples = "translate(" + tx + ", " + ty + ") "; | |
transformString.sankeySeq = {}; | |
transformString.sankeySeq.single = "translate(" + props.paddingSingle.left + ", 20) "; | |
transformString.sankeySeq.multiples = "translate(" + padding.left + ", " + padding.top + ") " + "scale(" + sx + ", " + sy + ")"; | |
transformString.pTop = {}; | |
transformString.pTop.single = "translate(" + (padding.left + width / 2) + ", " + (padding.top - 3) + ")"; | |
transformString.pTop.multiples = "translate(" + (padding.left + width / 2 * sx) + ", " + (padding.top - 3) + ")"; | |
transformString.pLeft = {}; | |
transformString.pLeft.single = "translate(" + (padding.left - 3) + ", " + (padding.top + height / 2) + ")"; | |
transformString.pLeft.multiples = "translate(" + (padding.left - 3) + ", " + (padding.top + height / 2 * sy) + ")"; | |
return transformString; | |
} | |
function transitionToSingle(clickedElement, _trans) { | |
var trans = (typeof _trans !== 'undefined') ? _trans : d3.transition().duration(1000); | |
d3.selectAll("g.sankeyFrame") | |
.each(function(d) { | |
if (this === clickedElement) { | |
console.log("clicked"); | |
d3.select(this) // transition frame | |
.transition(trans) | |
.attr("transform", "translate(0, 0)"); | |
d3.select(this).selectAll("g.pTop") // transition top padding | |
.transition(trans) | |
.attr("transform", function(d) {return d.single;}); | |
d3.select(this).selectAll("text.col.multiples") // hide col title on top | |
.transition(trans) | |
.style("opacity", 0); | |
d3.select(this).selectAll("text.col.single") // display row + col title on top | |
.transition(trans) | |
.style("opacity", 1); | |
d3.select(this).selectAll("text.nodeLabel") // display nodeLabels | |
.transition(trans) | |
.style("opacity", 1); | |
d3.select(this).selectAll("g.pLeft") // hide left padding g | |
.transition(trans) | |
.style("opacity", 0); | |
d3.select(this).selectAll("g.sankeySeq") // transition graph | |
.transition(trans) | |
.attr("transform", function(d) {return d.single;}); | |
d3.selectAll(".axis") // show axes for sequence and categories | |
.transition().delay(800) | |
.style("opacity", 1); | |
d3.selectAll(".coverSankeySeq") // hide surrounding rectangle | |
.transition(trans) | |
.style("opacity", 0); | |
} else { | |
console.log("not clicked"); | |
d3.select(this) | |
.transition(trans) | |
.attr("transform", function(d) {return d.single;}) | |
.style("opacity", 0); | |
} | |
}); | |
return false; | |
} | |
function transitionToMultiples(clickedElement) { | |
var trans = d3.transition() | |
.duration(1000); | |
d3.selectAll("g.sankeyFrame") | |
.each(function(d) { | |
if (this === clickedElement) { | |
console.log("clicked"); | |
d3.select(this) | |
.transition(trans) | |
.attr("transform", function(d) { return d.multiples;}); | |
d3.select(this).selectAll("g.pTop") // transition top padding | |
.transition(trans) | |
.attr("transform", function(d) {return d.multiples;}); | |
d3.select(this).selectAll("text.col.multiples") // display col title on top | |
.transition(trans) | |
.style("opacity", 1); | |
d3.select(this).selectAll("text.col.single") // hide row + col title on top | |
.transition(trans) | |
.style("opacity", 0); | |
d3.select(this).selectAll("text.nodeLabel") // hide nodeLabels | |
.transition(trans) | |
.style("opacity", 0); | |
d3.select(this).selectAll("g.pLeft") // display left padding g | |
.transition(trans) | |
.style("opacity", 1); | |
d3.select(this).selectAll("g.sankeySeq") // transition graph | |
.transition(trans) | |
.attr("transform", function(d) {return d.multiples;}); | |
d3.selectAll(".axis") | |
.transition(trans) | |
.style("opacity", 0); | |
d3.selectAll(".coverSankeySeq") | |
.transition(trans) | |
.style("opacity", 1); | |
} else { | |
d3.select(this) | |
.transition(trans) | |
.attr("transform", function(d) {return d.multiples;}) | |
.style("opacity", 1);; | |
} | |
}); | |
return true; | |
} | |
function displaySankeyMenu(selection) { | |
var div1 = selection.append("div").attr("class", "sankeyMenu"); | |
// options menu | |
var divOm = div1.append("div").attr("class", "OptionsMenu"); | |
divOm.append("div") | |
.attr("class", "titleMenu") | |
.append("label") | |
.text("options"); | |
if (!(allGraphs.cols === 1 && allGraphs.rows === 1)) { | |
var div2 = divOm.append("span") | |
.attr("class", "span1") | |
.append("input") | |
.attr("class", "nodeScaling") | |
.attr("type", "checkbox") | |
.attr("value", "global") | |
.attr("checked", "checked") | |
.on("change", updateScaling); | |
divOm.select("span.span1") | |
.append("label") | |
.text("global scale"); | |
divOm.append("br"); | |
} | |
var div3 = divOm.append("span") | |
.attr("class", "span2") | |
.append("input") | |
.attr("class", "labelOnOff") | |
.attr("type", "checkbox") | |
.attr("value", showNodeLabels) | |
.attr("checked", "checked") | |
.on("change", updateNodeLabels); | |
div1.select("span.span2") | |
.append("label") | |
.text("node labels"); | |
// node info menu | |
if (typeof nodeFile === 'undefined') { return;} | |
if (debugOn) { | |
console.log("nodeInfoKeys: "); | |
console.log(nodeInfoKeys); | |
} | |
var divNim = d3.select("div.sankeyMenu") | |
.append("div") | |
.attr("class", "NodeInfoMenu"); | |
divNim.append("div") | |
.attr("class", "titleMenu") | |
.append("label") | |
.text("node info"); | |
divNim = divNim.append("form") | |
.selectAll("span") | |
.data(nodeInfoKeys) | |
.enter() | |
.append("span"); | |
divNim.append("input") | |
.attr("type", "radio") | |
.attr("name", "menu") | |
.attr("value", function(d) { return d; }) | |
.attr('checked', function(d, i) { if (i === 0) { return 'checked' }; }) | |
.on("change", function change() { | |
nodeInfoKey = this.value; | |
console.log("nodeInfokey: "+ nodeInfoKey); | |
updateNodeInfo(); | |
}); | |
divNim.append("label") | |
.text(function(d) { return d; }); | |
divNim.append("br"); | |
} | |
function updateScaling() { | |
var mySankey; | |
var parentSelector; | |
var graph; | |
var trans = d3.transition().duration(1000); | |
var currentValue = d3.select(".nodeScaling").attr("value"); | |
if (currentValue == "global") { | |
d3.select(".nodeScaling").attr("value", "local"); | |
scaleGlobal = false; | |
} | |
else { | |
d3.select(".nodeScaling").attr("value", "global"); | |
scaleGlobal = true; | |
} | |
allGraphs.forEach( function (col, colIndex) { | |
col.forEach( function (container, rowIndex) { | |
mySankey = container.sankey; | |
graph = container.graph; | |
if (scaleGlobal) {mySankey.maxValue(allGraphs.maxValue);} | |
else {mySankey.maxValue(-1);} | |
mySankey.layout(); | |
// transition links | |
parentSelector = "g.sankeySeq.s" + colIndex + "-" + rowIndex; | |
d3.select(parentSelector).selectAll(".link") | |
.data(graph.links, function(d) { return d.id; }) // data join for clarity. Data attributes have been changed even without join! | |
.transition(trans) | |
.attr("d", mySankey.link()) | |
.style("stroke-width", function(d) { return Math.max(1, d.dy) + "px"; }); | |
// transition nodes | |
d3.select(parentSelector).selectAll(".node") | |
.data(graph.nodes) | |
.transition(trans) | |
.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"; }); | |
d3.select(parentSelector).selectAll("rect.sankeyNode") | |
.transition(trans) | |
.attr("height", function(d) { return d.dy; }); | |
d3.select(parentSelector).selectAll("text.nodeLabel") | |
.transition(trans) | |
.attr("y", function(d) { return d.dy / 2; }); | |
d3.select(parentSelector).selectAll("rect.sankeyNodeInfo") | |
.filter(function(d) { nodeInfoKeys.forEach( function(key) { | |
if (key !== nodeInfoNone) {// skip case for no nodeInfo selection | |
d.nodeInfos[key + "_dy"] = mySankey.getNodeHeight(+d.nodeInfos[key]); | |
} | |
else { | |
if (nodeInfoKey === nodeInfoNone) { | |
d3.selectAll("rect.sankeyNodeInfo").attr("y", function(d) {return d.dy;}); | |
} | |
} | |
}); | |
return (typeof d.nodeInfos !== 'undefined'); }) | |
.attr("height", function(d) { | |
return d3.select(this).attr("height"); | |
}) | |
.transition(trans) | |
.attr("y", function(d) { | |
if (nodeInfoKey === nodeInfoNone) { return d.dy; } | |
else { | |
if (debugOn) { | |
console.log("value: " + +d.nodeInfos[nodeInfoKey]); | |
console.log("newHeight: " + d.nodeInfos[nodeInfoKey + "_dy"]); | |
} | |
return d.dy - d.nodeInfos[nodeInfoKey + "_dy"]; | |
} | |
}) | |
.attr("height", function(d) { | |
if (nodeInfoKey === nodeInfoNone) { return 0; } | |
else {return d.nodeInfos[nodeInfoKey + "_dy"]; } | |
}); | |
}); | |
}); | |
} | |
function updateNodeLabels() { | |
var currentValue = d3.select(".labelOnOff").attr("value"); | |
if (currentValue === "on") { | |
d3.select(".labelOnOff").attr("value", "off"); | |
d3.selectAll("text.nodeLabel").style("display", "none"); | |
} | |
else { | |
d3.select(".labelOnOff").attr("value", "on"); | |
d3.selectAll("text.nodeLabel").style("display", "block"); | |
} | |
} | |
function updateNodeInfo() { | |
var trans = d3.transition().duration(1000); | |
d3.select("div.NodeInfoMenu") | |
.transition(trans) | |
.style("border-color", function() { return (nodeInfoKey === nodeInfoNone) ? "rgba(0,0,0,0.1)" : "orange";}) | |
.style("background-color", function() { | |
return (nodeInfoKey === nodeInfoNone) ? "rgba(0,0,0,0.1)" : "rgba(255,165,0,0.1)";}); | |
d3.selectAll("rect.sankeyNodeInfo") | |
.transition(trans) | |
.attr("y", function(d) { | |
if (nodeInfoKey === nodeInfoNone) { return d.dy; } | |
else { | |
if (debugOn) { | |
console.log("value: " + +d.nodeInfos[nodeInfoKey]); | |
console.log("newHeight: " + d.nodeInfos[nodeInfoKey + "_dy"]); | |
} | |
return d.dy - d.nodeInfos[nodeInfoKey + "_dy"]; | |
} | |
}) | |
.attr("height", function(d) { | |
if (nodeInfoKey === nodeInfoNone) { return 0; } | |
else {return d.nodeInfos[nodeInfoKey + "_dy"]; } | |
}); | |
}; | |
//////////////////////////////////////////////////// | |
// 4.0 add visualization specific processing here // | |
//////////////////////////////////////////////////// | |
function createChart(selection, _file) { | |
allGraphs = constructSankeyFromCSV(_file); // main data structure build from csv file | |
var sankey; // sankeySeq | |
var width; // width of drawing area within SVG | |
var height; // height of drawing area within SVG | |
var sankeyG; // group element containing the sankeySeq graph | |
var sankeyF; // group element containing the sankeyFrame = sankeySeq graph + axes/titles | |
var link; // selection with paths | |
var node; // selection with nodes | |
var graph; // graph for each sankeySeq | |
var props; // properties calculated in initialization function | |
var multiplesOn = true; // small multiples currently shown? | |
var transformString; // object that transform strings for transitioning between small multiples and single | |
var svg; | |
var sx, sy, tx, ty; // variables for the transform | |
selection.each(function () { | |
// 4.1 insert code here | |
displaySankeyMenu(selection); | |
svg = d3.select(this).append("div") | |
.attr("class", "sankeyChart") | |
.append("svg") | |
.attr("width", size[0]) | |
.attr("height", size[1]) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// drawing axes | |
props = initialize_whp_and_axes(svg, width, height); | |
if (allGraphs.cols === 1 && allGraphs.rows === 1) { d3.selectAll(".axis").style("opacity", 1);} | |
width = props.width; | |
height = props.height; | |
if (debugOn) { console.log("allGraphs:"); console.log(allGraphs); } | |
allGraphs.forEach( function (col, colIndex) { | |
col.forEach( function (container, rowIndex) { | |
graph = container.graph; | |
if (debugOn) { | |
console.log("col: " + container.dimCol); | |
console.log("row: " + container.dimRow); | |
} | |
// setting up sankeySeq | |
sankey = d3.sankeySeq() | |
.size([width, height]) | |
.sequence(sequence) | |
.categories(categories) | |
.nodeWidth(nodeWidth) | |
.nodePadding(nodePadding) | |
.nodes(graph.nodes) | |
.links(graph.links) | |
.debugOn(debugOn); | |
if (scaleGlobal) {sankey.maxValue(allGraphs.maxValue);} | |
sankey.layout(); | |
container.sankey = sankey; | |
transformString = initializeFrame(svg, props, allGraphs, colIndex, rowIndex); | |
sankeyF = svg.append("g") | |
.datum(transformString.sankeyFrame) | |
.attr("class", "sankeyFrame f" + colIndex + "-" + rowIndex) | |
.attr("transform", transformString.sankeyFrame.multiples) | |
.on("click", function () { | |
if (allGraphs.cols === 1 && allGraphs.rows === 1) { return;} | |
multiplesOn = (multiplesOn) ? transitionToSingle(this) : transitionToMultiples(this); | |
}); | |
sankeyG = sankeyF.append("g") | |
.datum(transformString.sankeySeq) | |
.attr("class", "sankeySeq s" + colIndex + "-" + rowIndex) | |
.attr("transform", transformString.sankeySeq.multiples); | |
var topP = sankeyF.append("g") | |
.datum(transformString.pTop) | |
.attr("class", "sankeyPad multiples pTop") | |
.attr("transform", transformString.pTop.multiples); | |
topP.append("text") | |
.attr("class", "multiples col c" + colIndex) | |
.text(function() { | |
var topLabel = ""; // for single Sankey no title | |
if (allGraphs.cols === 1 && allGraphs.rows > 1) { topLabel = container.dimRow;} // switch label to top | |
else { | |
topLabel = container.dimCol; | |
} | |
return topLabel; | |
}); | |
topP.append("text") | |
.attr("class", "single col c" + colIndex) | |
.style("opacity", 0) | |
.text(function() { | |
var topLabel = ""; // for single Sankey no title | |
if (allGraphs.cols === 1 && allGraphs.rows > 1) { topLabel = container.dimRow;} // switch label to top | |
else { | |
topLabel = container.dimRow + "\u00A0\u00A0\u00A0\u00A0" + container.dimCol; | |
} | |
return topLabel; | |
}); | |
sankeyF.append("g") | |
.datum(transformString.pLeft) | |
.attr("class", "sankeyPad multiples pLeft") | |
.attr("transform", transformString.pLeft.multiples) | |
.append("text") | |
.attr("class", "multiples row r" + rowIndex) | |
.attr("transform", "rotate(-90)") | |
.text(function() { | |
var topLabel = ""; // for single Sankey and one dimension no title | |
if (allGraphs.cols > 1 && allGraphs.rows > 1) { topLabel = container.dimRow;} | |
return topLabel; | |
}); | |
// rect as selection area | |
sankeyG.append("rect") | |
.attr("class", "coverSankeySeq") | |
.attr("height", height) | |
.attr("width", width) | |
.style("stroke", "black") | |
.style("stroke-width", 1) | |
.style("fill-opacity", 0); | |
// tooltip | |
var tooltip = d3.select("body").append("div").attr("class", "tooltip"); | |
// drawing links | |
link = sankeyG.append("g").selectAll(".link") | |
.data(graph.links) | |
.enter().append("path") | |
.attr("class", function(d) { return "link" + " l" + d.id.replace(">","").replace(" ", "_"); }) | |
.attr("d", sankey.link()) | |
.style("stroke-width", function(d) { return Math.max(1, d.dy) + "px"; }) | |
.sort(function(a, b) { return b.dy - a.dy; }) | |
.on("mouseover", function(d) { | |
var info = sequenceName + ": " + d.source.nameX + " -> " + d.target.nameX; | |
info += "<br>" + categoryName + ": " + d.source.nameY + " -> " + d.target.nameY; | |
info += "<br>" + valueName + ": " + d.value; | |
tooltip.html(info); | |
tooltip.style("visibility", "visible"); | |
}) | |
.on("mousemove", function() { | |
return tooltip.style("top", (d3.event.pageY-30)+"px").style("left", (d3.event.pageX+10)+"px"); | |
}) | |
.on("mouseout", function() { | |
return tooltip.style("visibility", "hidden"); | |
}); | |
// drawing nodes | |
node = sankeyG.append("g").selectAll(".node") | |
.data(graph.nodes) | |
.enter().append("g") | |
.attr("class", "node") | |
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); | |
node.append("rect") | |
.attr("class", function(d) { return "sankeyNode" + " n" + d.nameX.replace(" ", "_") + "_" + d.nameY.replace(" ", "_"); }) | |
.attr("height", function(d) { return d.dy; }) | |
.attr("width", sankey.nodeWidth()) | |
.on("mouseover", function(d) { | |
var info = sequenceName + ": " + d.nameX; | |
info += "<br>" + categoryName + ": " + d.nameY; | |
info += "<br>" + valueName + ": " + d.value; | |
tooltip.html(info); | |
tooltip.style("visibility", "visible"); | |
}) | |
.on("mousemove", function() { | |
return tooltip.style("top", (d3.event.pageY-30)+"px").style("left", (d3.event.pageX+10)+"px"); | |
}) | |
.on("mouseout", function() { | |
return tooltip.style("visibility", "hidden"); | |
}); | |
node.append("text") | |
.attr("class", "nodeLabel") | |
.attr("x", 3 + sankey.nodeWidth()) | |
.attr("y", function(d) { return d.dy / 2; }) | |
.attr("dy", ".35em") | |
.attr("text-anchor", "start") | |
.attr("transform", null) | |
.style("opacity", 0) | |
.text(function(d) { return d.nameY; }) | |
.filter(function(d) { return d.x > width * .9; }) | |
.attr("x", -3) | |
.attr("text-anchor", "end"); | |
// .attr("transform", function(d) { return "translate (" + ((d.dy / 2) + 30) + "," + (d.dy / 2) + ") rotate(90)"; }); | |
//drawing nodeInfos | |
node.filter(function(d) { return (typeof d.nodeInfos !== 'undefined'); }) // display nodeInfos | |
.append("rect") | |
.attr("class", "sankeyNodeInfo") | |
.attr("y", function(d) { return d.dy;}) | |
.attr("height", function(d) { | |
nodeInfoKeys.forEach( function(key) { | |
if (key !== nodeInfoNone) {// skip case for no nodeInfo selection | |
d.nodeInfos[key + "_dy"] = sankey.getNodeHeight(+d.nodeInfos[key]); | |
} | |
}); | |
return 0; }) | |
.attr("width", sankey.nodeWidth()) | |
.style("fill", "orange") | |
.on("mouseover", function(d) { | |
var info = sequenceName + ": " + d.nameX; | |
info += "<br>" + categoryName + ": " + d.nameY; | |
info += "<br>" + valueName + "("+ nodeInfoKey + "): " + d.nodeInfos[nodeInfoKey]; | |
tooltip.html(info); | |
tooltip.style("visibility", "visible"); | |
}) | |
.on("mousemove", function() { | |
return tooltip.style("top", (d3.event.pageY-30)+"px").style("left", (d3.event.pageX+10)+"px"); | |
}) | |
.on("mouseout", function() { | |
return tooltip.style("visibility", "hidden"); | |
}); | |
if (container.transform === "single") { | |
transitionToSingle(sankeyF.node(), d3.transition().duration(0)); | |
} | |
}); | |
}); | |
}); | |
} | |
// 4.2 update functions | |
//////////////////////////////////////////////////// | |
// 5.0 processing data begins here // | |
//////////////////////////////////////////////////// | |
// 5.1 adjust for visualization specific data processing | |
// XHR to load data | |
function readData(csvFile, selection) { | |
if (csvFile !== "<pre>") { | |
d3.csv(csvFile, function(error1, f) { | |
var nodeFileName = _myData.split(".")[0] + "_nodes." + _myData.split(".")[1]; | |
d3.csv(nodeFileName, function(error2, nf) { // load infos from optional nodeInfo file | |
if (debugOn) { | |
console.log("start"); | |
console.log(error2); | |
console.log(nf); | |
console.log("end"); | |
} | |
nodeFile = nf; | |
file = f; | |
if (debugOn) { | |
console.log("file: "); | |
console.log(file); | |
console.log("nodeFileName: "); | |
console.log(nodeFileName); | |
console.log("nodeFile: "); | |
console.log(nodeFile); | |
} | |
createChart(selection, f); | |
}); | |
}); | |
} | |
else { | |
file = d3.csvParse(d3.select("pre#data").text()); | |
nodeFile = d3.csvParse(d3.select("pre#dataNodes").text()); | |
if (nodeFile.length === 0) { | |
var a; | |
nodeFile = a; // set to undefined | |
} | |
if (debugOn) { | |
console.log("file: "); | |
console.log(file); | |
console.log("nodeFile: "); | |
console.log(nodeFile); | |
} | |
createChart(selection, file); | |
} | |
} | |
// processes sankey from a csv file. Returns the necessary graph data structure. | |
// based on the approach from timelyportfolio, see http://bl.ocks.org/timelyportfolio/5052095 | |
function constructSankeyFromCSV(_file) { | |
//set up graph in same style as original example but empty | |
var graph = {}; | |
var allGraphs = []; // array of graphs | |
var source, target; | |
var columns = _file.columns; | |
var dataGroups = []; | |
var nodeMap = d3.map(); | |
var hashKey; | |
var hashNode; | |
var data; // data from each group (categories of dimension) | |
var container; | |
var sourceValues, targetValues; // for iterating over value to find maxValue | |
var value, maxValue = 0; // maxValue for scaling option | |
console.log(_file.columns); | |
if (typeof valueName === 'undefined') {valueName = _file.columns[0]}; | |
// processing main file first | |
if (_file.columns.length === 5) { // standard case | |
dataGroups = d3.nest() | |
.key(function(d) { return ""; }) | |
.key(function(d) { return ""; }) | |
.entries(_file); | |
if (typeof nodeFile !== 'undefined') { | |
nodeFile.forEach(function(node){ | |
hashKey = node['sourceX'] + "," + node['sourceY']; | |
nodeMap.set(hashKey, node); | |
if (debugOn) { console.log(node);} | |
}); | |
nodeInfoKeys = nodeFile.columns; | |
nodeInfoKeys.splice(0,2,nodeInfoNone); | |
} | |
} else if (_file.columns.length === 6) { // one additional dimension | |
dataGroups = d3.nest() | |
.key(function(d) { return ""; }) | |
.key(function(d) { return d[columns[5]]; }).sortKeys(d3.ascending) | |
.entries(_file); | |
if (typeof nodeFile !== 'undefined') { | |
nodeFile.forEach(function(node){ | |
hashKey = node[_file.columns[5]] + "," + node['sourceX'] + "," + node['sourceY']; | |
nodeMap.set(hashKey, node); | |
if (debugOn) { console.log(node);} | |
}); | |
nodeInfoKeys = nodeFile.columns; | |
nodeInfoKeys.splice(0,3,nodeInfoNone); | |
} | |
} else if (_file.columns.length === 7) { // two additional dimensions | |
dataGroups = d3.nest() | |
.key(function(d) { return d[columns[6]]; }).sortKeys(d3.ascending) | |
.key(function(d) { return d[columns[5]]; }).sortKeys(d3.ascending) | |
.entries(_file); | |
if (debugOn) { console.log("----------- nodeInfos ------------");}; | |
if (typeof nodeFile !== 'undefined') { | |
nodeFile.forEach(function(node){ | |
hashKey = node[_file.columns[6]] + "," + node[_file.columns[5]] + "," + node['sourceX'] + "," + node['sourceY']; | |
nodeMap.set(hashKey, node); | |
if (debugOn) { console.log(node);} | |
}); | |
nodeInfoKeys = nodeFile.columns; | |
nodeInfoKeys.splice(0,4,nodeInfoNone); | |
} | |
} | |
allGraphs.rows = d3.nest() | |
.key(function(d) { return d[columns[5]];}) | |
.entries(_file) | |
.length; | |
allGraphs.cols = d3.nest() | |
.key(function(d) { return d[columns[6]];}) | |
.entries(_file) | |
.length; | |
if (debugOn) { | |
console.log("----------- datagroups ------------"); | |
console.log(dataGroups); | |
console.log("----------nodeMap----------"); | |
console.log(nodeMap); | |
} | |
// create data structure containing data in the right representation | |
dataGroups.forEach(function (dim2, col) { | |
allGraphs[col] = []; | |
dim2.values.forEach(function(dim1, row) { | |
data = dim1.values; | |
graph = {"nodes" : [], "links" : []}; | |
if (debugOn) {console.log("graph0: ");} | |
data.forEach(function (d, i) { | |
if (debugOn) { | |
console.log("i: " + i); | |
console.log(d); | |
} | |
// data is derived from the hard-coded column order | |
source = d[columns[1]] + "_" + d[columns[2]]; | |
target = d[columns[3]] + "_" + d[columns[4]]; | |
graph.nodes.push({ "name": source }); | |
graph.nodes.push({ "name": target }); | |
graph.links.push({ "source": source, | |
"target": target, | |
"id": source + "->" + target, | |
"value": +d[columns[0]] }); | |
}); | |
if (debugOn) { | |
console.log("graph1: "); | |
console.log(JSON.stringify(graph)); | |
} | |
graph.nodes = d3.nest() | |
.key(function (d) { return d.name; }) | |
.map(graph.nodes) | |
.keys(); | |
if (debugOn) { | |
console.log("graph2: "); | |
console.log(JSON.stringify(graph)); | |
} | |
// 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 node to make nodes an array of objects | |
// rather than an array of strings | |
graph.nodes.forEach(function (d, i) { | |
var xValue = d.split("_")[0]; | |
var yValue = d.split("_")[1]; | |
graph.nodes[i] = { "name": d, "nameX": xValue, "nameY": yValue }; | |
// add nodeInfos if available | |
var nInfos; | |
if (allGraphs.cols !== 1 && allGraphs.rows !== 1) { // two additional dimensions | |
nInfos = nodeMap.get(dim2.key + "," + dim1.key + "," + xValue + "," + yValue); | |
} else if (allGraphs.cols === 1 && dim1.rows !== 1) { // one additional dimension | |
nInfos = nodeMap.get(dim1.key + "," + xValue + "," + yValue); | |
} else { // standard case | |
nInfos = nodeMap.get(xValue + "," + yValue); | |
} | |
if (typeof nInfos !== 'undefined') { | |
Object.keys(nInfos).forEach(function(key) { | |
nInfos[key + "_dy"] = 0; | |
}); | |
graph.nodes[i].nodeInfos = nInfos; | |
} | |
}); | |
if (debugOn) { | |
console.log("graph 3"); | |
console.log(JSON.stringify(graph)); | |
console.log(graph); | |
} | |
// default setting of categories and sequence as an array sorted lexicographically | |
if (!sequence) { | |
sequence = d3.set(graph.nodes.map(function (d) {return d.nameX;})) | |
.values().sort(d3.ascending); | |
} | |
if (!categories) { | |
categories = d3.set(graph.nodes.map(function (d) {return d.nameY;})) | |
.values().sort(d3.ascending); | |
} | |
if (debugOn) { | |
console.log("categoriesSorted: "); | |
console.log(categories); | |
} | |
container = {}; | |
container.graph = graph; | |
container.dimRow = dim1.key; | |
container.dimCol = dim2.key; | |
// computing the value of the largest node | |
sourceValues = d3.nest() | |
.key(function(d) { return d.source; }) | |
.rollup(function(values) { return d3.sum(values, function(d) {return +d.value; }) }) | |
.entries(graph.links); | |
targetValues = d3.nest() | |
.key(function(d) { return d.target; }) | |
.rollup(function(values) { return d3.sum(values, function(d) {return +d.value; }) }) | |
.entries(graph.links); | |
sourceValues = sourceValues.concat(targetValues); | |
value = d3.max(sourceValues, function(d) { | |
return d.value;}); | |
maxValue = value > maxValue ? value : maxValue; | |
if (allGraphs.cols === 1 && allGraphs.rows === 1) { container.transform = "single";} // standard case | |
else { container.transform = "multiples";} // additional dimensions | |
allGraphs[col].push(container); | |
}); | |
}); | |
if (debugOn) {console.log("maxValue: " + maxValue);} | |
allGraphs.maxValue = maxValue; | |
return allGraphs; | |
} | |
return chartAPI; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment