Last active
December 17, 2018 23:26
-
-
Save mikelotis/16502b487b37730cf5fc22acc3d42f96 to your computer and use it in GitHub Desktop.
Edmonton - Mayor Expenses II
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
border: yes |
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"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<title>Mayor's Expenses II</title> | |
<style> | |
svg { | |
display: block; | |
margin: 0 auto; | |
} | |
.node { | |
cursor: pointer; | |
} | |
.node circle { | |
fill: #fff; | |
stroke: steelblue; | |
stroke-width: 1.5px; | |
} | |
.node text { | |
font: 10px sans-serif; | |
} | |
.link { | |
fill: none; | |
stroke: #ccc; | |
stroke-width: 1.5px; | |
} | |
#myBtn { | |
display: none; | |
position: fixed; | |
bottom: 50px; | |
right: 0px; | |
z-index: 99; | |
border: none; | |
outline: none; | |
color: white; | |
cursor: pointer; | |
padding: 15px; | |
background-color: #999; | |
border-radius: 4px; | |
font: 1.0em 'Roboto', sans-serif; | |
} | |
#myBtn:hover { | |
background-color:black; | |
} | |
</style> | |
</head> | |
<body> | |
<button onclick="topFunction()" id="myBtn" title="Go to top">Top</button> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> | |
<script src="makeJson.js"></script> | |
<script> | |
var log = console.log; | |
//svg | |
var margin = {top: 30, right: 30, bottom: 0, left: 55}; | |
var width = 1300 - margin.right - margin.left; | |
var height = 6000 - margin.top - margin.bottom; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.right + margin.left) | |
.attr("height", height + margin.bottom + margin.top) | |
.append('g') | |
.attr("transform", `translate(${margin.left}, ${margin.top})`); | |
// d3.select("svg").append("rect") | |
// .attr("width", width + margin.right + margin.left) | |
// .attr("height", height + margin.bottom + margin.top) | |
// .style("stroke", "black") | |
// .style("fill", "none"); | |
var diagonal = d3.svg.diagonal() | |
.projection(function(d) { return [d.y, d.x]; }); | |
var i = 0; | |
var duration = 750; | |
var root; | |
var tree = d3.layout.tree().size([height, width]); | |
d3.csv("https://gist.githubusercontent.com/mikelotis/830aad9ab59a1ec5932125f77ef323a8/raw/e7ed2e19f3108bcf0631b9c4cfa751e9ccb82809/Mayor_Expense_Report_2014-2016.csv", expensesTree); | |
//Builds, the data visualization | |
function expensesTree(error, data) { | |
if(error) throw Error; | |
root = makeJson(data); | |
// log(root); | |
root.x0 = 0; | |
root.y0 = 0; | |
function collapse(d) { | |
if(d.children) { | |
d._children = d.children; | |
d._children.forEach(collapse); | |
d.children = null; | |
}; | |
}; | |
root.children.forEach(collapse); | |
update(root); | |
}; | |
d3.select(self.frameElement).style("height", height); | |
function update(source) { | |
// Compute the new tree layout. | |
var nodes = tree.nodes(root).reverse(); | |
var links = tree.links(nodes); | |
// Normalize for fixed-depth. | |
nodes.forEach(function(d) { d.y = d.depth * 180; }); | |
// Update the nodes | |
var node = svg.selectAll("g.node") | |
.data(nodes, function(d) { return d.id || (d.id = ++i); }); | |
// Enter any new nodes at the parent's previous position. | |
var nodeEnter = node.enter().append('g') | |
.attr("class", "node") | |
.attr("transform", function(d) { return `translate(${source.y0},${source.x0})`; }) | |
.on("click", click); | |
nodeEnter.append("circle") | |
.attr("r", 1e-6) | |
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); | |
nodeEnter.append("text") | |
.attr("x", function(d) { return d.children || d._children ? -10 : 10; }) | |
.attr("dy", "0.35em") | |
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) | |
.text(function(d) { return d.name; }) | |
.style("fill-opacity", 1e-6); | |
// Transitions nodes to their new position | |
var nodeUpdate = node.transition() | |
.duration(duration) | |
.attr("transform", function(d){ return `translate(${d.y}, ${d.x})`; }); | |
nodeUpdate.select("circle") | |
.attr('r', 4.5) | |
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); | |
nodeUpdate.select("text") | |
.style("fill-opacity", 1); | |
// Transition exiting nodes to the parent's new position | |
var nodeExit = node.exit().transition() | |
.duration(duration) | |
.attr("transform", function(d){ return `translate(${source.y}, ${source.x})`; }) | |
.remove(); | |
nodeExit.select("circle") | |
.attr("r", 1e-6); | |
nodeExit.select("text") | |
.attr("fill-opacity", 1e-6); | |
// Update the links | |
var link = svg.selectAll("path.link") | |
.data(links, function(d) {return d.target.id; }); | |
// Enter any new links at the parent's previous position. | |
link.enter().insert("path", "g") | |
.attr("class", "link") | |
.attr("d", function(d) { | |
var o = {x: source.x0, y: source.y0}; | |
return diagonal({source: o, target: o}); | |
}); | |
// Transition links to thier new position | |
link.transition() | |
.duration(duration) | |
.attr("d", diagonal); | |
// Transition exiting nodes to the parent's new position | |
link.exit().transition() | |
.duration(duration) | |
.attr("d", function(d) { | |
var o = {x: source.x, y: source.y}; | |
return diagonal({source: o, target: o}); | |
}) | |
.remove(); | |
// Stash the old positions for transition. | |
nodes.forEach(function(d) { | |
d.x0 = d.x; | |
d.y0 = d.y; | |
}); | |
}; | |
// 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; | |
} | |
update(d); | |
}; | |
// When the user scrolls down 20px from the top of the document, show the button | |
window.onscroll = function() {scrollFunction()}; | |
function scrollFunction() { | |
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { | |
document.getElementById("myBtn").style.display = "block"; | |
} else { | |
document.getElementById("myBtn").style.display = "none"; | |
} | |
}; | |
// When the user clicks on the button, scroll to the top of the document | |
function topFunction() { | |
document.body.scrollTop = 0; | |
document.documentElement.scrollTop = 0; | |
}; | |
</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
//From CSV to JSON | |
var makeJson = function makeJson(data) { | |
//double nesting, depth-1.expenditure type and depth-2.Year | |
var expNest = d3.nest().key(function(d){ return d["Expenditure Type"]; }) | |
.key(function(d){ return d["Year"]; }) | |
.entries(data) | |
.map(function(d) { | |
//years values | |
var yearsFinal = d.values.map(function(p) { | |
//details values | |
var values = p.values.map(function(n) { | |
return { | |
name: `${n["Expenditure Detail"]} $${n["Cost"]} ${n["Year"]}` | |
}; | |
}); | |
//details nest | |
return { | |
name: p.key, | |
children: values | |
}; | |
}); | |
//years nest | |
return { | |
name: d.key, | |
children: yearsFinal | |
}; | |
}); | |
//years values with detailed values, in desired format | |
return {name:"Expenses", children: expNest}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment