Skip to content

Instantly share code, notes, and snippets.

@billdwhite
Last active August 29, 2015 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save billdwhite/9875443 to your computer and use it in GitHub Desktop.
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
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;
};
<!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>
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