(function (global) { var dispatch = d3.dispatch("click"); // ---- /*This software is released under the MIT License MIT License 2014 Denes Csala http://www.csaladen.es The following software uses the javascript frameworks below, all of which are distributed under the MIT or GNU/GPL license: D3.js http://d3js.org/ data-oriented javascript framework. - Sankey plugin http://bost.ocks.org/mike/sankey/ for D3.js (modified) by Mike Bostock, which is based on the initial version http://tamc.github.io/Sankey/ by Thomas Counsell. I have incorporated the ability to render Sankey cycles, as pioneered by https://github.com/cfergus - Dragdealer.js href="http://skidding.github.io/dragdealer/ by Ovidiu Chereches */ // var data={"nodes": [], "links": []} // nodesform=d3.select("#nodes-form"); function addnode() { var a=nodesform.append("div"); a.text(nodesform[0][0].children.length-1+' '); a.append("input").attr("value",'{"name":"New Node"}'); } function removenode() { nodesform[0][0].children[nodesform[0][0].children.length-1].remove("div") } linksform=d3.select("#links-form"); function addlink() { linksform.append("div").append("input").attr("value",'{"source":0,"target":1,"value":0.52}'); } function removelink() { linksform[0][0].children[linksform[0][0].children.length-1].remove("div") } function draw() { change(data); } function save(){ d3.select('#save').style('z-index',100).transition().style('opacity',0.9); st='{"sankey":{"nodes":[' for (i = 0; i < nodesform[0][0].children.length; i++) { st=st+nodesform[0][0].children[i].children[0].value+','; } st=st.substring(0, st.length - 1)+'],"links":['; for (i = 0; i < linksform[0][0].children.length; i++) { st=st+linksform[0][0].children[i].children[0].value+','; } st = st.substring(0, st.length - 1)+']},"params":['+densityslider.value.current[0]+','+opacityslider.value.current[0]+','+labelformat+','+labeltextformat+','+showlinkcount+']'; if (document.getElementById("fixedlayout").checked){ var coords=[] sankey.nodes().forEach(function(d){ coords.push([d.x,d.y]) }) st=st+',"fixedlayout":'+JSON.stringify(coords); } st=st+'}'; d3.select('#savetext').text(st); } function load(){ d3.select('#load').style('z-index',100).transition().style('opacity',0.9); } function loadsubmit(){ d3.select('#load').transition().style('opacity',0).style('z-index',-1); var rawtext=d3.select('#load')[0][0].children[1].value; if (rawtext!="") { //parse data var rawdata=JSON.parse(rawtext); if ("sankey" in rawdata) { var newdata=rawdata.sankey; } else { var newdata=rawdata; } var loadtext=JSON.stringify(newdata) //remove existing node entry boxes var n=nodesform[0][0].children.length; for (i = 0; i < n; i++) { nodesform[0][0].children[0].remove("div"); } //remove existing link entry boxes var n=linksform[0][0].children.length; for (i = 0; i < n; i++) { linksform[0][0].children[0].remove("div"); } //add new node entry boxes var newdata2=JSON.parse(loadtext.substring(loadtext.indexOf('"nodes":[')+8, loadtext.indexOf('"links":[')-1)); for (i = 0; i < newdata2.length; i++) { var a=nodesform.append("div"); a.text(nodesform[0][0].children.length-1+' '); a.append("input").attr("value",JSON.stringify(newdata2[i])); } //add new link entry boxes var newdata2=JSON.parse(loadtext.substring(loadtext.indexOf('"links":[')+8, loadtext.length - 1)) for (i = 0; i < newdata2.length; i++) { linksform.append("div").append("input").attr("value",JSON.stringify(newdata2[i])); } //set parameters if ("fixedlayout" in rawdata) { fixedlayout=document.getElementById("ignorelayout").checked?[]:rawdata.fixedlayout; } else { fixedlayout=[]; } if ("params" in rawdata) { labelformat=rawdata.params[2]; labeltextformat=rawdata.params[3]; if (rawdata.params.length>4) showlinkcount=rawdata.params[4]; else showlinkcount=0; document.getElementById("vlabel").checked=(labelformat==0)?true:false; document.getElementById("tlabel").checked=(labeltextformat==0)?true:false; document.getElementById("clabel").checked=(showlinkcount==1)?true:false; densityslider.setValue(rawdata.params[0]); opacityslider.setValue(rawdata.params[1]); } else { change(newdata); } } } // var parallelrendering=false; var minnodewidth = 50; var padding = 28; var labelformat = 0; var labeltextformat = 0; var showlinkcount = 0; var paddingmultiplier = 100; var lowopacity = 0.3; var highopacity = 0.7; var fixedlayout=[]; var sankeyColor = d3.scale.category20(); var sankeyNumberFormat = d3.format(",.0f"); // d3.select("#ndec") // .on("change",draw); // d3.select("#ldec") // .on("change",draw); var margin = { top: 10, right: 10, bottom: 10, left: 40 }; var width = 1000; //document.getElementById("chart").offsetWidth - margin.left - margin.right, var height = 500; // document.getElementById("chart").offsetHeight - margin.bottom; var svg = null;//d3.select("#chart").append("svg") //svg.append("rect").attr("x",0).attr("y",0).attr("width","100%").attr("height","100%").attr("fill","white").attr('class','background') //svg=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 svg background color via rectangle trick // d3.select("#chart").select("svg") var sankey = null; // var sankey = d3.sankey().nodeWidth(30).nodePadding(padding).size([width, height]); // var path = sankey.reversibleLink(); var change = function(d) { svg.selectAll("g").remove(); sankey = d3.sankey().nodeWidth(30).nodePadding(padding).size([width, height]); sankey.nodes(d.nodes).links(d.links).layout(500); var g = svg.append("g") //link .selectAll(".link").data(d.links).enter().append("g").attr("class", "link").sort(function(j, i) { return i.dy - j.dy }); var path = sankey.reversibleLink(); var h = g.append("path") //path0 .attr("d", path(0)); var f = g.append("path") //path1 .attr("d", path(1)); var e = g.append("path") //path2 .attr("d", path(2)); g.attr("fill", function(i) { if (i.fill) return i.fill; else if (i.source.fill) return i.source.fill; else return i.source.color = sankeyColor(i.source.name.replace(/ .*/, "")) }).attr("opacity", lowopacity).on("mouseover", function(d) { d3.select(this).style('opacity', highopacity); }).on("mouseout", function(d) { d3.select(this).style('opacity', lowopacity); }).append("title") .text(sankeyEdgeTitle); var c = svg.append("g") //node .selectAll(".node").data(d.nodes).enter().append("g").attr("class", "node").attr("transform", function(i) { return "translate(" + i.x + "," + i.y + ")" }).call(d3.behavior.drag().origin(function(i) { return i }).on("dragstart", function() { this.parentNode.appendChild(this) }).on("drag", samkeyOnDragMove)); c.append("rect") //node .attr("height", function(i) { return i.dy }).attr("width", sankey.nodeWidth()).style("fill", function(i) { if (i.fill) return i.color = i.fill; else return i.color = sankeyColor(i.name.replace(/ .*/, "")) }).style("stroke", function(i) { return d3.rgb(i.color).darker(2) }).on("mouseover", function(d) { svg.selectAll(".link").filter(function(l) { return l.source == d || l.target == d; }).transition().style('opacity', highopacity); }).on("mouseout", function(d) { svg.selectAll(".link").filter(function(l) { return l.source == d || l.target == d; }).transition().style('opacity', lowopacity); }).on("dblclick", function(d) { svg.selectAll(".link").filter(function(l) { return l.target == d; }).attr("display", function() { if (d3.select(this).attr("display") == "none") return "inline" else return "none" }); }).append("title").text(sankeyNodeTitle); c.append("text") //node .attr("x", -6).attr("y", function(i) { return i.dy / 2 }).attr("dy", ".35em").attr("text-anchor", "end").attr("font-size","16px") .text(sankeyNodeLabel) .filter(function(i) { return i.x < width / 2 }).attr("x", 6 + sankey.nodeWidth()).attr("text-anchor", "start") if (showlinkcount>0) c.append("text") //node .attr("x", -6).attr("y", function(i) { return i.dy / 2 + 20 }).attr("dy", ".35em").attr("text-anchor", "end").attr("font-size","16px") .text(function(i) { return "→ "+(i.targetLinks.length)+" | "+(i.sourceLinks.length)+" →"; }).filter(function(i) { return i.x < width / 2 }).attr("x", 6 + sankey.nodeWidth()).attr("text-anchor", "start") c.append("text") //node .attr("x", function(i) {return -i.dy / 2}) .attr("y", function(i) {return i.dx / 2 + 9}) .attr("transform", "rotate(270)").attr("text-anchor", "middle").attr("font-size","23px").text(function(i) { if ((i.dy>minnodewidth)&&(labelformat<1)){ return sankeyNumberFormat(i.value); } }).attr("fill",function(d){ return d3.rgb(d["color"]).brighter(2) }).attr("stroke",function(d){ return d3.rgb(d["color"]).darker(2) }).attr("stroke-width","1px"); svg.selectAll('rect') .on('mouseover', (a, b, c, d, e) => {console.log('mouseover', a, b, c, d, e)}) .on('mouseout', (a, b, c, d, e) => {console.log('mouseout', a, b, c, d, e)}) .on('mousedown', (a, b, c, d, e) => {console.log('mousedown', a, b, c, d, e)}) .on('mouseup', (a) => { dispatch.click({ nativeEvent: d3.event, node: a, element: d3.event.srcElement }) }); function samkeyOnDragMove(i) { // if (document.getElementById("ymove").checked) { // if (document.getElementById("xmove").checked) { // d3.select(this).attr("transform", "translate(" + (i.x = Math.max(0, Math.min(width - i.dx, d3.event.x))) + "," + (i.y = Math.max(0, Math.min(height - i.dy, d3.event.y))) + ")") // } else { dragged = true d3.select(this).attr("transform", "translate(" + i.x + "," + (i.y = Math.max(0, Math.min(height - i.dy, d3.event.y))) + ")") // } // } else { // if (document.getElementById("xmove").checked) { // d3.select(this).attr("transform", "translate(" + (i.x = Math.max(0, Math.min(width - i.dx, d3.event.x))) + "," + i.y + ")") // } // } sankey.relayout(); f.attr("d", path(1)); // TODO: global var f h.attr("d", path(0)); // TODO: global var h e.attr("d", path(2)); // TODO: global var e } }; // draw(); function sankeyEdgeTitle(i) { return i.source.name + " → " + i.target.name + "\n" + sankeyNumberFormat(i.value) } function sankeyNodeTitle(i) { if (i.label) return i.label + "\n" + sankeyNumberFormat(i.value) return i.name + "\n" + sankeyNumberFormat(i.value) } function sankeyNodeLabel(i) { if (labeltextformat < 1) { if (i.label) return (i.label.length > 20) ? i.label.substr(0, 20) + '...' : i.label return i.name; } else { return ""; } } var renderGraph = (htmlNode, data, opts) => { var opts = opts || {} if (!htmlNode) throw "Missing html node"; if (!data) throw "Missing data"; // console.log('renderGraph data', data) htmlNode.append("rect").attr("x",0).attr("y",0).attr("width","100%").attr("height","100%").attr("fill","white") margin = { top: 10, right: 20, bottom: 10, left: 40 }; width = (opts.width) ? opts.width - margin.left - margin.right : 1020; height = (opts.height) ? opts.height : 500; htmlNode.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom) svg = htmlNode.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); change(data); return dispatch } global.parallelrendering = parallelrendering global.fixedlayout = fixedlayout global.renderGraph = renderGraph })(window)