Skip to content

Instantly share code, notes, and snippets.

@emoruzzi
Forked from Neilos/README.md
Last active March 29, 2022 20:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save emoruzzi/6f8140c4d903e64ae35dce0c971e488f to your computer and use it in GitHub Desktop.
Save emoruzzi/6f8140c4d903e64ae35dce0c971e488f to your computer and use it in GitHub Desktop.
Bi-directional hierarchical sankey diagram

This is a demonstration of a bi-directional hierarchical sankey diagram produced in javascript, html and css using d3. (Refresh page to generate new random data)

Sankey diagrams represent flows between nodes by varying the thickness of the connecting links.

This diagram was based off of Mike Bostock's sankey diagram, but additionally incorporates bi-directionality into the flow and caters for hierarchical relationships between nodes to allow drill down into the data.

All javascript code to generate the diagram markup is contained in the app.js file, but the underlying calculations are performed using a custom plugin: bihisankey.js.

This version addresses the issue with double clicking. Demo: http://bl.ocks.org/emoruzzi/6f8140c4d903e64ae35dce0c971e488f

'use strict';
var svg, tooltip, biHiSankey, path, defs, colorScale, highlightColorScale, isTransitioning;
var OPACITY = {
NODE_DEFAULT: 0.9,
NODE_FADED: 0.1,
NODE_HIGHLIGHT: 0.8,
LINK_DEFAULT: 0.6,
LINK_FADED: 0.05,
LINK_HIGHLIGHT: 0.9
},
TYPES = ["Asset", "Expense", "Revenue", "Equity", "Liability"],
TYPE_COLORS = ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02", "#a6761d"],
TYPE_HIGHLIGHT_COLORS = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494"],
LINK_COLOR = "#b3b3b3",
INFLOW_COLOR = "#2E86D1",
OUTFLOW_COLOR = "#D63028",
NODE_WIDTH = 36,
COLLAPSER = {
RADIUS: NODE_WIDTH / 2,
SPACING: 2
},
OUTER_MARGIN = 10,
MARGIN = {
TOP: 2 * (COLLAPSER.RADIUS + OUTER_MARGIN),
RIGHT: OUTER_MARGIN,
BOTTOM: OUTER_MARGIN,
LEFT: OUTER_MARGIN
},
TRANSITION_DURATION = 400,
HEIGHT = 500 - MARGIN.TOP - MARGIN.BOTTOM,
WIDTH = 960 - MARGIN.LEFT - MARGIN.RIGHT,
LAYOUT_INTERATIONS = 32,
REFRESH_INTERVAL = 7000;
var formatNumber = function (d) {
var numberFormat = d3.format(",.0f"); // zero decimal places
return "£" + numberFormat(d);
},
formatFlow = function (d) {
var flowFormat = d3.format(",.0f"); // zero decimal places with sign
return "£" + flowFormat(Math.abs(d)) + (d < 0 ? " CR" : " DR");
},
// Used when temporarily disabling user interractions to allow animations to complete
disableUserInterractions = function (time) {
isTransitioning = true;
setTimeout(function(){
isTransitioning = false;
}, time);
},
hideTooltip = function () {
return tooltip.transition()
.duration(TRANSITION_DURATION)
.style("opacity", 0);
},
showTooltip = function () {
return tooltip
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + 15 + "px")
.transition()
.duration(TRANSITION_DURATION)
.style("opacity", 1);
};
colorScale = d3.scale.ordinal().domain(TYPES).range(TYPE_COLORS),
highlightColorScale = d3.scale.ordinal().domain(TYPES).range(TYPE_HIGHLIGHT_COLORS),
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 + ")");
svg.append("g").attr("id", "links");
svg.append("g").attr("id", "nodes");
svg.append("g").attr("id", "collapsers");
tooltip = d3.select("#chart").append("div").attr("id", "tooltip");
tooltip.style("opacity", 0)
.append("p")
.attr("class", "value");
biHiSankey = d3.biHiSankey();
// Set the biHiSankey diagram properties
biHiSankey
.nodeWidth(NODE_WIDTH)
.nodeSpacing(10)
.linkSpacing(4)
.arrowheadScaleFactor(0.5) // Specifies that 0.5 of the link's stroke WIDTH should be allowed for the marker at the end of the link.
.size([WIDTH, HEIGHT]);
path = biHiSankey.link().curvature(0.45);
defs = svg.append("defs");
defs.append("marker")
.style("fill", LINK_COLOR)
.attr("id", "arrowHead")
.attr("viewBox", "0 0 6 10")
.attr("refX", "1")
.attr("refY", "5")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", "1")
.attr("markerHeight", "1")
.attr("orient", "auto")
.append("path")
.attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");
defs.append("marker")
.style("fill", OUTFLOW_COLOR)
.attr("id", "arrowHeadInflow")
.attr("viewBox", "0 0 6 10")
.attr("refX", "1")
.attr("refY", "5")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", "1")
.attr("markerHeight", "1")
.attr("orient", "auto")
.append("path")
.attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");
defs.append("marker")
.style("fill", INFLOW_COLOR)
.attr("id", "arrowHeadOutlow")
.attr("viewBox", "0 0 6 10")
.attr("refX", "1")
.attr("refY", "5")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", "1")
.attr("markerHeight", "1")
.attr("orient", "auto")
.append("path")
.attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");
function update () {
var link, linkEnter, node, nodeEnter, collapser, collapserEnter;
function dragmove(node) {
node.x = Math.max(0, Math.min(WIDTH - node.width, d3.event.x));
node.y = Math.max(0, Math.min(HEIGHT - node.height, d3.event.y));
d3.select(this).attr("transform", "translate(" + node.x + "," + node.y + ")");
biHiSankey.relayout();
svg.selectAll(".node").selectAll("rect").attr("height", function (d) { return d.height; });
link.attr("d", path);
}
function containChildren(node) {
node.children.forEach(function (child) {
child.state = "contained";
child.parent = this;
child._parent = null;
containChildren(child);
}, node);
}
function expand(node) {
node.state = "expanded";
node.children.forEach(function (child) {
child.state = "collapsed";
child._parent = this;
child.parent = null;
containChildren(child);
}, node);
}
function collapse(node) {
node.state = "collapsed";
containChildren(node);
}
function restoreLinksAndNodes() {
link
.style("stroke", LINK_COLOR)
.style("marker-end", function () { return 'url(#arrowHead)'; })
.transition()
.duration(TRANSITION_DURATION)
.style("opacity", OPACITY.LINK_DEFAULT);
node
.selectAll("rect")
.style("fill", function (d) {
d.color = colorScale(d.type.replace(/ .*/, ""));
return d.color;
})
.style("stroke", function (d) {
return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
})
.style("fill-opacity", OPACITY.NODE_DEFAULT);
node.filter(function (n) { return n.state === "collapsed"; })
.transition()
.duration(TRANSITION_DURATION)
.style("opacity", OPACITY.NODE_DEFAULT);
}
function showHideChildren(node) {
disableUserInterractions(2 * TRANSITION_DURATION);
hideTooltip();
if (node.state === "collapsed") { expand(node); }
else { collapse(node); }
biHiSankey.relayout();
update();
link.attr("d", path);
restoreLinksAndNodes();
}
function highlightConnected(g) {
link.filter(function (d) { return d.source === g; })
.style("marker-end", function () { return 'url(#arrowHeadInflow)'; })
.style("stroke", OUTFLOW_COLOR)
.style("opacity", OPACITY.LINK_DEFAULT);
link.filter(function (d) { return d.target === g; })
.style("marker-end", function () { return 'url(#arrowHeadOutlow)'; })
.style("stroke", INFLOW_COLOR)
.style("opacity", OPACITY.LINK_DEFAULT);
}
function fadeUnconnected(g) {
link.filter(function (d) { return d.source !== g && d.target !== g; })
.style("marker-end", function () { return 'url(#arrowHead)'; })
.transition()
.duration(TRANSITION_DURATION)
.style("opacity", OPACITY.LINK_FADED);
node.filter(function (d) {
return (d.name === g.name) ? false : !biHiSankey.connected(d, g);
}).transition()
.duration(TRANSITION_DURATION)
.style("opacity", OPACITY.NODE_FADED);
}
link = svg.select("#links").selectAll("path.link")
.data(biHiSankey.visibleLinks(), function (d) { return d.id; });
link.transition()
.duration(TRANSITION_DURATION)
.style("stroke-WIDTH", function (d) { return Math.max(1, d.thickness); })
.attr("d", path)
.style("opacity", OPACITY.LINK_DEFAULT);
link.exit().remove();
linkEnter = link.enter().append("path")
.attr("class", "link")
.style("fill", "none");
linkEnter.on('mouseenter', function (d) {
if (!isTransitioning) {
showTooltip().select(".value").text(function () {
if (d.direction > 0) {
return d.source.name + " → " + d.target.name + "\n" + formatNumber(d.value);
}
return d.target.name + " ← " + d.source.name + "\n" + formatNumber(d.value);
});
d3.select(this)
.style("stroke", LINK_COLOR)
.transition()
.duration(TRANSITION_DURATION / 2)
.style("opacity", OPACITY.LINK_HIGHLIGHT);
}
});
linkEnter.on('mouseleave', function () {
if (!isTransitioning) {
hideTooltip();
d3.select(this)
.style("stroke", LINK_COLOR)
.transition()
.duration(TRANSITION_DURATION / 2)
.style("opacity", OPACITY.LINK_DEFAULT);
}
});
linkEnter.sort(function (a, b) { return b.thickness - a.thickness; })
.classed("leftToRight", function (d) {
return d.direction > 0;
})
.classed("rightToLeft", function (d) {
return d.direction < 0;
})
.style("marker-end", function () {
return 'url(#arrowHead)';
})
.style("stroke", LINK_COLOR)
.style("opacity", 0)
.transition()
.delay(TRANSITION_DURATION)
.duration(TRANSITION_DURATION)
.attr("d", path)
.style("stroke-WIDTH", function (d) { return Math.max(1, d.thickness); })
.style("opacity", OPACITY.LINK_DEFAULT);
node = svg.select("#nodes").selectAll(".node")
.data(biHiSankey.collapsedNodes(), function (d) { return d.id; });
node.transition()
.duration(TRANSITION_DURATION)
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
.style("opacity", OPACITY.NODE_DEFAULT)
.select("rect")
.style("fill", function (d) {
d.color = colorScale(d.type.replace(/ .*/, ""));
return d.color;
})
.style("stroke", function (d) { return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1); })
.style("stroke-WIDTH", "1px")
.attr("height", function (d) { return d.height; })
.attr("width", biHiSankey.nodeWidth());
node.exit()
.transition()
.duration(TRANSITION_DURATION)
.attr("transform", function (d) {
var collapsedAncestor, endX, endY;
collapsedAncestor = d.ancestors.filter(function (a) {
return a.state === "collapsed";
})[0];
endX = collapsedAncestor ? collapsedAncestor.x : d.x;
endY = collapsedAncestor ? collapsedAncestor.y : d.y;
return "translate(" + endX + "," + endY + ")";
})
.remove();
nodeEnter = node.enter().append("g").attr("class", "node");
nodeEnter
.attr("transform", function (d) {
var startX = d._parent ? d._parent.x : d.x,
startY = d._parent ? d._parent.y : d.y;
return "translate(" + startX + "," + startY + ")";
})
.style("opacity", 1e-6)
.transition()
.duration(TRANSITION_DURATION)
.style("opacity", OPACITY.NODE_DEFAULT)
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("text");
nodeEnter.append("rect")
.style("fill", function (d) {
d.color = colorScale(d.type.replace(/ .*/, ""));
return d.color;
})
.style("stroke", function (d) {
return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
})
.style("stroke-WIDTH", "1px")
.attr("height", function (d) { return d.height; })
.attr("width", biHiSankey.nodeWidth());
node.on("mouseenter", function (g) {
if (!isTransitioning) {
restoreLinksAndNodes();
highlightConnected(g);
fadeUnconnected(g);
d3.select(this).select("rect")
.style("fill", function (d) {
d.color = d.netFlow > 0 ? INFLOW_COLOR : OUTFLOW_COLOR;
return d.color;
})
.style("stroke", function (d) {
return d3.rgb(d.color).darker(0.1);
})
.style("fill-opacity", OPACITY.LINK_DEFAULT);
tooltip
.style("left", g.x + MARGIN.LEFT + "px")
.style("top", g.y + g.height + MARGIN.TOP + 15 + "px")
.transition()
.duration(TRANSITION_DURATION)
.style("opacity", 1).select(".value")
.text(function () {
var additionalInstructions = g.children.length ? "\n(Double click to expand)" : "";
return g.name + "\nNet flow: " + formatFlow(g.netFlow) + additionalInstructions;
});
}
});
node.on("mouseleave", function () {
if (!isTransitioning) {
hideTooltip();
restoreLinksAndNodes();
}
});
/**
* Fix to allow for dblclick on dragging element
* This essentially checks to see if the vectors are in the same location once the drag
* has ended.
*/
var lastvector = []
function isclicked(node){
try {
if( lastvector[node.id].toString() !== [node.x,node.y].toString() ){
throw 'no match';
}
showHideChildren(node);
}catch(err) {
lastvector[node.id] = [node.x,node.y]
}
}
// allow nodes to be dragged to new positions
node.call(d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", function () { node.event,this.parentNode.appendChild(this); })
.on("dragend", isclicked)
.on("drag", dragmove));
// add in the text for the nodes
node.filter(function (d) { return d.value !== 0; })
.select("text")
.attr("x", -6)
.attr("y", function (d) { return d.height / 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 + biHiSankey.nodeWidth())
.attr("text-anchor", "start");
collapser = svg.select("#collapsers").selectAll(".collapser")
.data(biHiSankey.expandedNodes(), function (d) { return d.id; });
collapserEnter = collapser.enter().append("g").attr("class", "collapser");
collapserEnter.append("circle")
.attr("r", COLLAPSER.RADIUS)
.style("fill", function (d) {
d.color = colorScale(d.type.replace(/ .*/, ""));
return d.color;
});
collapserEnter
.style("opacity", OPACITY.NODE_DEFAULT)
.attr("transform", function (d) {
return "translate(" + (d.x + d.width / 2) + "," + (d.y + COLLAPSER.RADIUS) + ")";
});
collapserEnter.on("dblclick", showHideChildren);
collapser.select("circle")
.attr("r", COLLAPSER.RADIUS);
collapser.transition()
.delay(TRANSITION_DURATION)
.duration(TRANSITION_DURATION)
.attr("transform", function (d, i) {
return "translate("
+ (COLLAPSER.RADIUS + i * 2 * (COLLAPSER.RADIUS + COLLAPSER.SPACING))
+ ","
+ (-COLLAPSER.RADIUS - OUTER_MARGIN)
+ ")";
});
collapser.on("mouseenter", function (g) {
if (!isTransitioning) {
showTooltip().select(".value")
.text(function () {
return g.name + "\n(Double click to collapse)";
});
var highlightColor = highlightColorScale(g.type.replace(/ .*/, ""));
d3.select(this)
.style("opacity", OPACITY.NODE_HIGHLIGHT)
.select("circle")
.style("fill", highlightColor);
node.filter(function (d) {
return d.ancestors.indexOf(g) >= 0;
}).style("opacity", OPACITY.NODE_HIGHLIGHT)
.select("rect")
.style("fill", highlightColor);
}
});
collapser.on("mouseleave", function (g) {
if (!isTransitioning) {
hideTooltip();
d3.select(this)
.style("opacity", OPACITY.NODE_DEFAULT)
.select("circle")
.style("fill", function (d) { return d.color; });
node.filter(function (d) {
return d.ancestors.indexOf(g) >= 0;
}).style("opacity", OPACITY.NODE_DEFAULT)
.select("rect")
.style("fill", function (d) { return d.color; });
}
});
collapser.exit().remove();
}
var exampleNodes = [
{"type":"Asset","id":"a","parent":null,"name":"Assets"},
{"type":"Asset","id":1,"parent":"a","number":"101","name":"Cash"},
{"type":"Asset","id":2,"parent":"a","number":"120","name":"Accounts Receivable"},
{"type":"Asset","id":3,"parent":"a","number":"140","name":"Merchandise Inventory"},
{"type":"Asset","id":4,"parent":"a","number":"150","name":"Supplies"},
{"type":"Asset","id":5,"parent":"a","number":"160","name":"Prepaid Insurance"},
{"type":"Asset","id":6,"parent":"a","number":"170","name":"Land"},
{"type":"Asset","id":7,"parent":"a","number":"175","name":"Buildings"},
{"type":"Asset","id":8,"parent":"a","number":"178","name":"Acc. Depreciation Buildings"},
{"type":"Asset","id":9,"parent":"a","number":"180","name":"Equipment"},
{"type":"Asset","id":10,"parent":"a","number":"188","name":"Acc. Depreciation Equipment"},
{"type":"Liability","id":"l","parent":null,"number":"l","name":"Liabilities"},
{"type":"Liability","id":11,"parent":"l","number":"210","name":"Notes Payable"},
{"type":"Liability","id":12,"parent":"l","number":"215","name":"Accounts Payable"},
{"type":"Liability","id":13,"parent":"l","number":"220","name":"Wages Payable"},
{"type":"Liability","id":14,"parent":"l","number":"230","name":"Interest Payable"},
{"type":"Liability","id":15,"parent":"l","number":"240","name":"Unearned Revenues"},
{"type":"Liability","id":16,"parent":"l","number":"250","name":"Mortage Loan Payable"},
{"type":"Equity","id":"eq","parent":null,"number":"eq","name":"Equity"},
{"type":"Revenue","id":"r","parent":null,"number":"r","name":"Revenues"},
{"type":"Revenue","id":"or","parent":"r","number":"","name":"Operating Revenue"},
{"type":"Revenue","id":17,"parent":"or","number":"310","name":"Service Revenues"},
{"type":"Revenue","id":"nor","parent":"r","number":"","name":"Non-Operating Revenue"},
{"type":"Revenue","id":18,"parent":"nor","number":"810","name":"Interest Revenues"},
{"type":"Revenue","id":19,"parent":"nor","number":"910","name":"Asset Sale Gain"},
{"type":"Revenue","id":20,"parent":"nor","number":"960","name":"Asset Sale Loss"},
{"type":"Expense","id":"ex","parent":null,"number":"ex","name":"Expenses"},
{"type":"Expense","id":21,"parent":"ex","number":"500","name":"Salaries Expense"},
{"type":"Expense","id":22,"parent":"ex","number":"510","name":"Wages Expense"},
{"type":"Expense","id":23,"parent":"ex","number":"540","name":"Supplies Expense"},
{"type":"Expense","id":24,"parent":"ex","number":"560","name":"Rent Expense"},
{"type":"Expense","id":25,"parent":"ex","number":"570","name":"Utilities Expense"},
{"type":"Expense","id":26,"parent":"ex","number":"576","name":"Telephone Expense"},
{"type":"Expense","id":27,"parent":"ex","number":"610","name":"Advertising Expense"},
{"type":"Expense","id":28,"parent":"ex","number":"750","name":"Depreciation Expense"}
]
var exampleLinks = [
{"source":8, "target":28, "value":Math.floor(Math.random() * 100)},
{"source":17, "target":18, "value":Math.floor(Math.random() * 100)},
{"source":22, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":3, "target":13, "value":Math.floor(Math.random() * 100)},
{"source":24, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":5, "target":4, "value":Math.floor(Math.random() * 100)},
{"source":15, "target":5, "value":Math.floor(Math.random() * 100)},
{"source":18, "target":8, "value":Math.floor(Math.random() * 100)},
{"source":3, "target":20, "value":Math.floor(Math.random() * 100)},
{"source":17, "target":18, "value":Math.floor(Math.random() * 100)},
{"source":22, "target":5, "value":Math.floor(Math.random() * 100)},
{"source":4, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":26, "target":16, "value":Math.floor(Math.random() * 100)},
{"source":27, "target":6, "value":Math.floor(Math.random() * 100)},
{"source":23, "target":4, "value":Math.floor(Math.random() * 100)},
{"source":10, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":17, "target":16, "value":Math.floor(Math.random() * 100)},
{"source":5, "target":12, "value":Math.floor(Math.random() * 100)},
{"source":12, "target":16, "value":Math.floor(Math.random() * 100)},
{"source":19, "target":5, "value":Math.floor(Math.random() * 100)},
{"source":15, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":27, "target":2, "value":Math.floor(Math.random() * 100)},
{"source":26, "target":28, "value":Math.floor(Math.random() * 100)},
{"source":22, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":3, "target":18, "value":Math.floor(Math.random() * 100)},
{"source":18, "target":5, "value":Math.floor(Math.random() * 100)},
{"source":25, "target":28, "value":Math.floor(Math.random() * 100)},
{"source":12, "target":1, "value":Math.floor(Math.random() * 100)},
{"source":28, "target":21, "value":Math.floor(Math.random() * 100)},
{"source":9, "target":16, "value":Math.floor(Math.random() * 100)},
{"source":14, "target":23, "value":Math.floor(Math.random() * 100)},
{"source":6, "target":1, "value":Math.floor(Math.random() * 100)},
{"source":9, "target":15, "value":Math.floor(Math.random() * 100)},
{"source":16, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":22, "target":28, "value":Math.floor(Math.random() * 100)},
{"source":8, "target":21, "value":Math.floor(Math.random() * 100)},
{"source":22, "target":7, "value":Math.floor(Math.random() * 100)},
{"source":18, "target":10, "value":Math.floor(Math.random() * 100)},
{"source":"eq", "target":1, "value":Math.floor(Math.random() * 100)},
{"source":1, "target":21, "value":Math.floor(Math.random() * 100)},
{"source":1, "target":24, "value":Math.floor(Math.random() * 100)},
{"source":17, "target":1, "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)},
{"source":Math.ceil(Math.random() * 28), "target":Math.ceil(Math.random() * 28), "value":Math.floor(Math.random() * 100)}
]
biHiSankey
.nodes(exampleNodes)
.links(exampleLinks)
.initializeNodes(function (node) {
node.state = node.parent ? "contained" : "collapsed";
})
.layout(LAYOUT_INTERATIONS);
disableUserInterractions(2 * TRANSITION_DURATION);
update();
<!DOCTYPE html>
<html>
<head>
<title>bihisankey diagram</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<div id="chart"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://cdn.rawgit.com/Neilos/bihisankey/master/bihisankey.js"></script>
<script src="app.js"></script>
</body>
</html>
.node rect {
cursor: move;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 1px 1px 2px #fff;
font-size: 0.8em;
font-family: sans-serif;
}
#tooltip {
position: absolute;
pointer-events: none;
font-size: 0.7em;
font-family: sans-serif;
padding: 3px;
width: auto;
height: auto;
background-color: #F2F2F2;
-webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.4);
-mox-box-shadow: 0px 0px 0px 5px rgba(0, 0, 0, 0.4);
box-shadow: 0px 0px 5px rbga(0, 0, 0, 0.4);
pointer-events: none;
}
.value {
white-space: pre-line;
margin: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment