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("
", 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 += "
" + categoryName + ": " + d.source.nameY + " -> " + d.target.nameY; info += "
" + 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 += "
" + categoryName + ": " + d.nameY; info += "
" + 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 += "
" + categoryName + ": " + d.nameY; info += "
" + 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 !== "") { 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; };