|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<title>2015 대한민국 세출 예산예산</title> |
|
<script src="http://d3js.org/d3.v2.js"></script> |
|
<script src="http://code.jquery.com/jquery-1.7.1.js"></script> |
|
<link rel="stylesheet" href="style.css"> |
|
<!-- <script src="http://code.jquery.com/jquery-2.1.3.min.js"></script> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> --> |
|
<!-- http://bl.ocks.org/davetaz/9954190 --> |
|
<!-- http://data.gov.uk/dataset/home-office-flight-data/resource/a65aa73a-4062-4eec-ad59-0bd881316f4d --> |
|
|
|
<p id="chart"></p> |
|
|
|
<!-- <p id="rawdata"> --> |
|
|
|
<script> |
|
/* |
|
* If running inside bl.ocks.org we want to resize the iframe to fit both graphs |
|
* This bit of code was shared originally at https://gist.github.com/benjchristensen/2657838 |
|
*/ |
|
if(parent.document.getElementsByTagName("iframe")[0]) { |
|
parent.document.getElementsByTagName("iframe")[0].setAttribute('style', 'height: 700px !important'); |
|
} |
|
|
|
var margin = {top: 40, right: 0, bottom: 0, left: 0}, |
|
width = $('#chart').width(), |
|
height = $('#chart').height() - margin.top - margin.bottom, |
|
formatNumber = d3.format(".2s"), |
|
transitioning; |
|
|
|
/* create x and y scales */ |
|
var x = d3.scale.linear() |
|
.domain([0, width]) |
|
.range([0, width]); |
|
|
|
var y = d3.scale.linear() |
|
.domain([0, height]) |
|
.range([0, height]); |
|
|
|
var treemap = d3.layout.treemap() |
|
.children(function(d, depth) { return depth ? null : d.children; }) |
|
.sort(function(a, b) { return a.value - b.value; }) |
|
.ratio(height / width * 0.5 * (1 + Math.sqrt(5))) |
|
.round(false); |
|
|
|
/* create svg */ |
|
var svg = d3.select("#chart").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.bottom + margin.top) |
|
.style("margin-left", -margin.left + "px") |
|
.style("margin.right", -margin.right + "px") |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")") |
|
.style("shape-rendering", "crispEdges"); |
|
|
|
var color = d3.scale.category20(); |
|
|
|
var grandparent = svg.append("g") |
|
.attr("class", "grandparent"); |
|
|
|
grandparent.append("rect") |
|
.attr("y", -margin.top) |
|
.attr("width", width) |
|
.attr("height", margin.top); |
|
|
|
grandparent.append("text") |
|
.attr("x", 6) |
|
.attr("y", -3 - margin.top) |
|
.attr("dy", "2em"); |
|
|
|
function initialize(root) { |
|
root.x = root.y = 0; |
|
root.dx = width; |
|
root.dy = height; |
|
root.depth = 0; |
|
} |
|
|
|
// Aggregate the values for internal nodes. This is normally done by the |
|
// treemap layout, but not here because of the custom implementation. |
|
function accumulate(d) { |
|
return d.children |
|
? d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0) |
|
: d.value; |
|
} |
|
|
|
// Compute the treemap layout recursively such that each group of siblings |
|
// uses the same size (1×1) rather than the dimensions of the parent cell. |
|
// This optimizes the layout for the current zoom state. Note that a wrapper |
|
// object is created for the parent node for each group of siblings so that |
|
// the parent’s dimensions are not discarded as we recurse. Since each group |
|
// of sibling was laid out in 1×1, we must rescale to fit using absolute |
|
// coordinates. This lets us use a viewport to zoom. |
|
function layout(d) { |
|
if (d.children) { |
|
treemap.nodes({children: d.children}); |
|
d.children.forEach(function(c) { |
|
c.x = d.x + c.x * d.dx; |
|
c.y = d.y + c.y * d.dy; |
|
c.dx *= d.dx; |
|
c.dy *= d.dy; |
|
c.parent = d; |
|
layout(c); |
|
}); |
|
} |
|
} |
|
|
|
/* display shows the treemap and writes the embedded transition function */ |
|
function display(d) { |
|
/* create grandparent bar at top */ |
|
grandparent |
|
.datum(d.parent) |
|
.on("click", transition) |
|
.select("text") |
|
.text(name(d)); |
|
|
|
var g1 = svg.insert("g", ".grandparent") |
|
.datum(d) |
|
.attr("class", "depth"); |
|
/* add in data */ |
|
var g = g1.selectAll("g") |
|
.data(d.children) |
|
.enter().append("g"); |
|
|
|
/* transition on child click */ |
|
g.filter(function(d) { return d.children; }) |
|
.classed("children", true) |
|
.on("click", function(d){ |
|
if(d.children | d.children.length !== 1) { |
|
transition(d); |
|
} |
|
}); |
|
|
|
/* write children rectangles */ |
|
g.selectAll(".child") |
|
.data(function(d) { return d.children || [d]; }) |
|
.enter().append("rect") |
|
.attr("class", "child") |
|
.call(rect) |
|
.append("title") |
|
.text(function(d) { return d.name + " " + formatNumber((typeof d.size == "undefined") ? '' : d.size); }); |
|
|
|
/* write parent rectangle */ |
|
g.append("rect") |
|
.attr("class", "parent") |
|
.call(rect) |
|
/* open new window based on the json's URL value for leaf nodes */ |
|
/* Chrome displays this on top */ |
|
.on("click", function(d) { |
|
if(!d.children | d.children.length == 1) { |
|
event.preventDefault(); |
|
$(this).unbind('click'); |
|
} |
|
}) |
|
.append("title") |
|
.text(function(d) { return d.name + " " + formatNumber(d.value); }); /*should be d.value*/ |
|
|
|
/* Adding a foreign object instead of a text object, allows for text wrapping */ |
|
g.append("foreignObject") |
|
.call(rect) |
|
/* open new window based on the json's URL value for leaf nodes */ |
|
/* Firefox displays this on top */ |
|
.on("click", function(d) { |
|
if(!d.children){ |
|
console.log(d); |
|
// window.open(d.url); |
|
} |
|
}) |
|
.attr("class","foreignobj") |
|
.append("xhtml:div") |
|
.attr("dy", "1.75em") |
|
.html(function(d) { |
|
if (d.size) { |
|
return d.name + " (" + formatNumber(d.size) + ")"; |
|
} |
|
if (d.value > 0 && typeof(d.value) !== "undefined") { |
|
return d.name + " (" + formatNumber(d.value) + ")"; |
|
} |
|
return d.name; |
|
}) |
|
.attr("class","textdiv"); //textdiv class allows us to style the text easily with CSS |
|
/* create transition function for transitions */ |
|
function transition(d) { |
|
if (transitioning || !d) return; |
|
transitioning = true; |
|
var g2 = display(d), |
|
t1 = g1.transition().duration(1750), |
|
t2 = g2.transition().duration(1750); |
|
// Update the domain only after entering new elements. |
|
x.domain([d.x, d.x + d.dx]); |
|
y.domain([d.y, d.y + d.dy]); |
|
// Enable anti-aliasing during the transition. |
|
svg.style("shape-rendering", null); |
|
|
|
// Draw child nodes on top of parent nodes. |
|
svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; }); |
|
// Fade-in entering text. |
|
g2.selectAll("text").style("fill-opacity", 0); |
|
g2.selectAll("foreignObject div").style("display", "none"); /*added*/ |
|
|
|
// Transition to the new view. |
|
t1.selectAll("text").call(text).style("fill-opacity", 0); |
|
t2.selectAll("text").call(text).style("fill-opacity", 1); |
|
t1.selectAll("rect").call(rect); |
|
t2.selectAll("rect").call(rect); |
|
|
|
t1.selectAll(".textdiv").style("display", "none"); /* added */ |
|
t1.selectAll(".foreignobj").call(foreign); /* added */ |
|
t2.selectAll(".textdiv").style("display", "block"); /* added */ |
|
t2.selectAll(".foreignobj").call(foreign); /* added */ |
|
|
|
// Remove the old node when the transition is finished. |
|
t1.remove().each("end", function() { |
|
svg.style("shape-rendering", "crispEdges"); |
|
transitioning = false; |
|
}); |
|
|
|
}//endfunc transition |
|
|
|
return g; |
|
}//endfunc display |
|
|
|
function text(text) { |
|
text.attr("x", function(d) { return x(d.x) + 6; }) |
|
.attr("y", function(d) { return y(d.y) + 6; }); |
|
} |
|
|
|
function rect(rect) { |
|
rect.attr("x", function(d) { return x(d.x); }) |
|
.attr("y", function(d) { return y(d.y); }) |
|
.attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) |
|
.attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }) |
|
.style("background", function(d) { return d.parent ? color(d.name) : null; }); |
|
} |
|
|
|
function foreign(foreign){ /* added */ |
|
foreign.attr("x", function(d) { return x(d.x); }) |
|
.attr("y", function(d) { return y(d.y); }) |
|
.attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) |
|
.attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }); |
|
} |
|
|
|
function name(d) { |
|
return d.parent ? name(d.parent) + "." + d.name : d.name; |
|
} |
|
|
|
function loadJSONFile(file) { |
|
/* load in data, display root */ |
|
d3.json(file, function(root) { |
|
loadData(root); |
|
}); |
|
} |
|
|
|
function loadData(root) { |
|
initialize(root); |
|
accumulate(root); |
|
layout(root); |
|
display(root); |
|
} |
|
|
|
function reSortRoot(root,value_key) { |
|
//console.log("Calling"); |
|
for (var key in root) { |
|
if (key == "key") { |
|
root.name = root.key; |
|
delete root.key; |
|
} |
|
if (key == "values") { |
|
root.children = []; |
|
for (item in root.values) { |
|
root.children.push(reSortRoot(root.values[item],value_key)); |
|
} |
|
delete root.values; |
|
} |
|
if (key == value_key) { |
|
root.value = parseFloat(root[value_key]); |
|
delete root[value_key]; |
|
} |
|
} |
|
return root; |
|
} |
|
|
|
$( document ).ready(function() { |
|
|
|
// d3.csv("http://bl.ocks.org/davetaz/raw/9954190/UK_Home_Office_Air_Travel_Data_2011.csv", function(csv_data){ |
|
d3.csv('data/2015-budget-by-dep.csv', function(csv_data){ |
|
|
|
// Add, remove or change the key values to change the hierarchy. |
|
var nested_data = d3.nest() |
|
.key(function(d) { return d.department; }) |
|
// .key(function(d) { return d.budget_type; }) |
|
.key(function(d) { return d.project; }) |
|
// .key(function(d) { return d.Destination; }) |
|
// .key(function(d) { return d.Supplier_name; }) |
|
// .key(function(d) { return d.Ticket_class_description; }) |
|
.entries(csv_data); |
|
|
|
// Creat the root node for the treemap |
|
var root = {}; |
|
|
|
// Add the data to the tree |
|
root.key = "2015 대한민국 세출 예산"; |
|
root.values = nested_data; |
|
|
|
// Change the key names and children values from .next and add values for a chosen column to define the size of the blocks |
|
root = reSortRoot(root,"budget"); |
|
|
|
// DEBUG |
|
// $("#rawdata").html(JSON.stringify(root)); |
|
|
|
loadData(root); |
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
</script> |