Last active
August 29, 2015 13:57
-
-
Save billdwhite/9875443 to your computer and use it in GitHub Desktop.
Alternative chart-flow.js which does not rely on jQuery and does not create SVG for you; Instead it simple appends flow layout to the provided SVG container, allowing more than one flow layout within an SVG
This file contains 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
d3.custom = {}; | |
d3.custom.chart = {}; | |
d3.custom.chart.flow = function() { | |
// public variables with default settings | |
var margin = {top:10, right:10, bottom:10, left:10}, // defaults | |
padding = {top:20, right:10, bottom:10, left:10}, | |
transitionDuration = 300, | |
chartGroup, | |
container, | |
svg, | |
width, | |
height, | |
root, | |
rootNode, | |
scrollbarAffordance; | |
var flow = d3.custom.layout.flow() | |
.margin(margin) | |
.padding(padding) | |
.nodeWidth(110) | |
.nodeHeight(30) | |
.containerHeight(20); | |
function chart(selection) { | |
rootNode = selection.node(); | |
function debounce(fn, timeout) { | |
var timeoutID = -1; | |
return function() { | |
if (timeoutID > -1) { | |
window.clearTimeout(timeoutID); | |
} | |
timeoutID = window.setTimeout(fn, timeout); | |
} | |
} | |
function resize(selectedNode) { | |
var domContainerWidth = parseInt(rootNode.style["width"]), | |
domContainerHeight = parseInt(rootNode.style["height"]), | |
flowWidth = 0; | |
if (root.height > domContainerHeight) { | |
scrollbarAffordance = 0; | |
} else { | |
scrollbarAffordance = 0; | |
} | |
flowWidth = domContainerWidth - scrollbarAffordance; | |
flow.width(flowWidth); | |
chart.update(selectedNode); | |
container.transition().duration(transitionDuration) | |
.attr("width", function(d) { | |
return domContainerWidth; | |
}) | |
.attr("height", function(d) { | |
return d.height + margin.top + margin.bottom; | |
}) | |
.select(".chartGroup") | |
.attr("width", function(d) { | |
return flowWidth; | |
}) | |
.attr("height", function(d) { | |
return d.height + margin.top + margin.bottom; | |
}) | |
.select(".background") | |
.attr("width", function(d) { | |
return flowWidth; | |
}) | |
.attr("height", function(d) { | |
return d.height + margin.top + margin.bottom; | |
}); | |
} | |
d3.select(window).on('resize', function() { | |
debounce(resize, 50)(); | |
}); | |
rootNode.onresize = function() { | |
debounce(resize, 50)(); | |
}; | |
selection.each(function(arg) { | |
root = arg; | |
container = d3.select(this); | |
var i = 0; | |
chart.update = function(source) { | |
var nodes = flow(root); | |
function color(d) { | |
return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c"; | |
} | |
// Toggle children on click. | |
function click(d) { | |
if (d.children) { | |
d._children = d.children; | |
d.children = null; | |
} else { | |
d.children = d._children; | |
d._children = null; | |
} | |
resize(d); | |
} | |
// Update the nodes… | |
var node = container.selectAll("g.node") | |
.data(nodes, function(d) { return d.id || (d.id = ++i); }); | |
var nodeEnter = node.enter().append("svg:g") | |
.attr("class", "node") | |
.attr("transform", function(d) { | |
return "translate(" + source.x + "," + source.y + ")"; | |
}) | |
.style("opacity", 1e-6); | |
// Enter any new nodes at the parent's previous position. | |
nodeEnter.append("svg:rect") | |
.attr("class", "background") | |
.attr("height", function(d) { return d.height; }) | |
.attr("width", function(d) { return d.width; }) | |
.style("fill", color) | |
.on("click", click); | |
nodeEnter.each(function(d) { | |
if (d.children || d._children) { | |
d3.select(this) | |
.append("path") | |
.attr("class", "expander") | |
.attr("d", "M 0 0 L 6 6 L 0 6 z") | |
.attr("transform", function(d) { | |
return d._children ? "translate(8,14)rotate(225)" : "translate(5,8)rotate(315)"; | |
}); | |
d3.select(this).append("svg:text") | |
.attr("class", "label") | |
.attr("dy", 13) | |
.attr("dx", 17) | |
.text(function(d) { return d.name; }); | |
} else { | |
d3.select(this).append("svg:text") | |
.attr("class", "label") | |
.attr("dy", 13) | |
.attr("dx", 4) | |
.text(function(d) { return d.name; }); | |
} | |
}); | |
// Transition nodes to their new position. | |
nodeEnter.transition() | |
.duration(transitionDuration) | |
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) | |
.style("opacity", 1); | |
var nodeUpdate = node.transition() | |
.duration(transitionDuration); | |
nodeUpdate.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) | |
.style("opacity", 1) | |
.select("rect") | |
.style("fill", color); | |
nodeUpdate.each(function(d) { | |
if (d.children || d._children) { | |
d3.select(this).select(".expander").transition() | |
.duration(transitionDuration) | |
.attr("transform", function(d) { | |
return d._children ? "translate(8,14)rotate(225)" : "translate(5,8)rotate(315)"; | |
}); | |
} | |
}); | |
nodeUpdate.select(".background") | |
.attr("height", function(d) { return d.height; }) | |
.attr("width", function(d) { return d.width; }); | |
// Transition exiting nodes to the parent's new position. | |
node.exit().transition() | |
.duration(transitionDuration) | |
.attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; }) | |
.style("opacity", 1e-6) | |
.remove(); | |
}; | |
resize(root); | |
chart.update(root); | |
}); | |
} | |
chart.width = function(value) { | |
if (!arguments.length) return width; | |
width = parseInt(value); | |
return this; | |
}; | |
chart.height = function(value) { | |
if (!arguments.length) return height; | |
height = parseInt(value); | |
return this; | |
}; | |
chart.margin = function(_) { | |
if (!arguments.length) return margin; | |
margin.top = typeof _.top != 'undefined' ? _.top : margin.top; | |
margin.right = typeof _.right != 'undefined' ? _.right : margin.right; | |
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; | |
margin.left = typeof _.left != 'undefined' ? _.left : margin.left; | |
return chart; | |
}; | |
return chart; | |
}; |
This file contains 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> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Flow Tree Example - Multiple Layouts in Single SVG</title> | |
<style> | |
body { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
} | |
.flowtree { | |
margin: 10px; | |
padding: 10px; | |
height: 900px; | |
width: 100%; | |
overflow-x: hidden; | |
overflow-y: scroll; | |
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.25), 0 0 6px rgba(0, 0, 0, 0.1); | |
border: 1px solid #AAAAAA; | |
} | |
.chartGroup .background { | |
fill: #FFFFFF; | |
stroke-width: 0.5; | |
} | |
.svg { | |
width: 100%; | |
height: 100%; | |
} | |
.node rect { | |
cursor: pointer; | |
fill: #FFFFFF; | |
fill-opacity: 0.5; | |
stroke: #333333; | |
stroke-width: 1px; | |
} | |
.node text { | |
font: 10px sans-serif; | |
pointer-events: none; | |
} | |
.ui-resizable-e { | |
width: 25px !important; | |
} | |
</style> | |
<script src="./d3.js" type="text/javascript"></script> | |
<script src="./chart-flow.js"></script> | |
<script src="./layout.js"></script> | |
</head> | |
<body> | |
<div id="container1" class="flowtree"></div> | |
</div> | |
<script type="text/javascript"> | |
var mainSVG = d3.select("#container1") | |
.append("svg") | |
.attr("class", "svg"); | |
var outerContainer = mainSVG.append("g").attr("class", "outer"); | |
var leftContainer = outerContainer.append("g") | |
.attr("class", "left") | |
.attr("transform", "translate(0, 0)") | |
.style("width", "400px") | |
.style("height", "1000px"); | |
var rightContainer = outerContainer.append("g") | |
.attr("class", "right") | |
.attr("transform", "translate(450, 0)") | |
.style("width", "400px") | |
.style("height", "1000px"); | |
leftContainer.append("rect") | |
.attr("fill", "#FFCAA8") | |
.attr("stroke", "#000000") | |
.attr("width", 400) | |
.attr("height", 1000); | |
rightContainer.append("rect") | |
.attr("fill", "#AECEFF") | |
.attr("stroke", "#000000") | |
.attr("width", 400) | |
.attr("height", 1000); | |
var flowTree = d3.custom.chart.flow(); | |
d3.json("./flare.json", function(data) { | |
leftContainer.datum(data) | |
.call(flowTree) | |
.on("mouseover.customHover", function() { | |
console.log("customHover"); | |
}) | |
.on("resize.custom", function() { | |
console.log("resize.custom"); | |
}); | |
}); | |
var flowTree1 = d3.custom.chart.flow(); | |
d3.json("./flare.json", function(data) { | |
rightContainer.datum(data) | |
.call(flowTree1) | |
.on("mouseover.customHover", function() { | |
console.log("customHover"); | |
}) | |
.on("resize.custom", function() { | |
console.log("resize.custom"); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains 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
d3.custom.layout = {}; | |
d3.custom.layout.flow = function() { | |
var hierarchy = d3.layout.hierarchy().sort(null).value(null), | |
nodeWidth = 125, | |
nodeHeight = 50, | |
containerHeight = 20, | |
width = 900, | |
height = 0, | |
padding = {top:20, left:10, bottom:10, right:10}, | |
margin = {top:10, left:10, bottom:10, right:10}; | |
function flow(d, i) { | |
var nodes = hierarchy.call(this, d, i), | |
root = nodes[0]; | |
function firstWalk(node) { | |
var children = node.children; | |
if (children && children.length > 0) { | |
var n = children.length, | |
i = -1, | |
child; | |
while (++i < n) { | |
child = children[i]; | |
firstWalk(child); | |
} | |
gridLayout(node, children, node.depth); | |
} else { | |
node.width = node._children ? width - (node.depth * (padding.left + padding.right)) - (padding.left + padding.right) : nodeWidth; | |
node.height = node._children ? containerHeight : nodeHeight; | |
} | |
} | |
function secondWalk(node) { | |
var children = node.children; | |
if (children && children.length > 0) { | |
var i = -1, | |
n = children.length, | |
child; | |
while (++i < n) { | |
child = children[i]; | |
child.x += node.x; | |
child.y += node.y; | |
secondWalk(child); | |
} | |
} | |
} | |
function gridLayout(node, children, depth) { | |
var paddingValue = node.parent ? padding.left + padding.right : margin.left + margin.right; | |
var availableWidth = width - (depth * (paddingValue)) - (paddingValue), | |
currentX = padding.left, | |
currentY = padding.top, | |
tallestChildHeight = 0; | |
children.forEach(function(child) { | |
if ((currentX + child.width + padding.right) >= availableWidth) { | |
currentX = padding.right; | |
currentY += tallestChildHeight; | |
tallestChildHeight = 0; | |
} | |
child.x = currentX; | |
child.y = currentY; | |
currentX += child.width + padding.right; | |
tallestChildHeight = Math.max(tallestChildHeight, child.height + padding.bottom); | |
}); | |
node.width = availableWidth; | |
node.height = currentY + tallestChildHeight; | |
node.x = node.parent ? padding.left : margin.left; | |
node.y = node.parent ? padding.top : margin.top; | |
} | |
firstWalk(root); | |
secondWalk(root); | |
height = root.height; | |
return nodes; | |
} | |
flow.padding = function(_) { | |
if (!arguments.length) return padding; | |
padding.top = typeof _.top != 'undefined' ? _.top : padding.top; | |
padding.right = typeof _.right != 'undefined' ? _.right : padding.right; | |
padding.bottom = typeof _.bottom != 'undefined' ? _.bottom : padding.bottom; | |
padding.left = typeof _.left != 'undefined' ? _.left : padding.left; | |
return this; | |
}; | |
flow.margin = function(_) { | |
if (!arguments.length) return margin; | |
flow.top = typeof _.top != 'undefined' ? _.top : flow.top; | |
flow.right = typeof _.right != 'undefined' ? _.right : flow.right; | |
flow.bottom = typeof _.bottom != 'undefined' ? _.bottom : flow.bottom; | |
flow.left = typeof _.left != 'undefined' ? _.left : flow.left; | |
return this; | |
}; | |
flow.width = function(value) { | |
if (!arguments.length) return width; | |
width = parseInt(value); | |
return this; | |
}; | |
flow.height = function(value) { | |
if (!arguments.length) return height; | |
height = parseInt(value); | |
return this; | |
}; | |
flow.nodeWidth = function(value) { | |
if (!arguments.length) return nodeWidth; | |
nodeWidth = parseInt(value); | |
return this; | |
}; | |
flow.nodeHeight = function(value) { | |
if (!arguments.length) return nodeHeight; | |
nodeHeight = parseInt(value); | |
return this; | |
}; | |
flow.containerHeight = function(value) { | |
if (!arguments.length) return containerHeight; | |
containerHeight = parseInt(value); | |
return this; | |
}; | |
return flow; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment