index.html
<!DOCTYPE html>
<meta charset="utf-8">
<title>映画興行収入ツリーマップ</title>
<style>
html, body {
margin:0px;
padding:0px;
}
form {
margin-left:4px;
}
text {
font: 10px sans-serif;
}
tspan:last-child {
font-size: 9px;
fill-opacity: 0.8;
}
.node rect {
shape-rendering: crispEdges;
}
.node--hover rect {
stroke: #000;
}
.tooltip {
position: absolute;
min-width: 150px;
z-index: 10;
padding: 0;
background-color: #ffffff;
color: #222222;
font-size: 16px;
border: 0;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
-moz-box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
-ms-box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
opacity: 1;
box-sizing: border-box;
pointer-events: none;
transform: translate(26px, -5px);
}
.tooltip {
h4 {
font-size: 16px;
font-weight: bold;
margin: 4px 0 12px 0;
padding: 0 0 12px 0;
border-bottom: 1px solid #e8e8e8;
}
h5 {
font-size: 16px;
margin: 0 0 3px 0;
padding: 0 0 0 0;
}
}
.tooltip .tooltip_container {
padding: 10px;
background-color: #ffffff;
text-align: center;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
&:after {
content: "";
position: absolute;
bottom: -5px;
height: 10px;
left: 50%;
margin-left: -5px;
width: 10px;
z-index: -1;
right: auto;
top: auto;
margin-top: auto;
background-color: rgb(255, 255, 255);
-webkit-box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
-moz-box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
-ms-box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
box-shadow: rgba(0, 0, 0, 0.247059) 0px 1px 3px;
-webkit-transform: rotate(45deg) scale(1);
-moz-transform: rotate(45deg) scale(1);
-ms-transform: rotate(45deg) scale(1);
transform: rotate(45deg) scale(1);
}
}
#contenner {
width: 960px;
}
#info {
width: 200px;
margin: 10px auto;
}
.total {
font-size: 0.8em;
opacity: 0.7;
}
.unit {
font-size: 0.8em;
}
.count,
.persent {
font-weight: bold;
margin-right: 2px;
}
</style>
<div id="contenner">
<form id="form" name="form" action="">
<input id="Checkbox1" type="checkbox" value="実写化" data-color="#ffcccc" checked="checked"/><label for="Checkbox1">実写化</label>
<input id="Checkbox2" type="checkbox" value="アニメ" data-color="#ccffff" /><label for="Checkbox2">アニメ</label>
<input id="Checkbox3" type="checkbox" value="特撮" data-color="#ccffcc" /><label for="Checkbox3">特撮</label>
<input id="Checkbox4" type="checkbox" value="ドラマ" data-color="#ffccff" /><label for="Checkbox4">TVドラマ</label>
<input id="Checkbox5" type="checkbox" value="小説" data-color="#ccccff" /><label for="Checkbox5">小説原作</label>
<input id="Checkbox6" type="checkbox" value="その他" data-color="#ffffaf" /><label for="Checkbox6">その他</label>
<div id="info"></div>
</form>
<svg width="960" height="1060"></svg>
</div>
<script src="//d3js.org/d3.v4.0.0-alpha.35.min.js"></script>
<script>
var total = 0;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var format = d3.format(".1f");
var color = ["#333", "#ccc", "#fff"];
var stratify = d3.stratify()
.parentId(function(d) { return d.id.substring(0, d.id.lastIndexOf(".")); });
var treemap = d3.treemap()
.size([width, height])
.paddingOuter(3)
.paddingTop(19)
.paddingInner(1)
.round(true);
var cast = function(d){
Object.keys(d).forEach(function(key){
d[key] = d[key].trim();
if (d[key] && !isNaN(+d[key])) d[key] = +d[key];
});
return d;
};
d3.tsv("movie.tsv", cast, function(error, data) {
if (error) throw error;
var nested = d3.nest()
.rollup(function(d){ return d[0]; })
.key(function(d){ return d.uid ;})
.map(data);
var root = stratify(data)
.sum(function(d) { return d["興収(単位:億円)"] ; })
.sort(function(a, b) { return b["興収(単位:億円)"] - a["興収(単位:億円)"]; });
treemap(root);
var cell = svg
.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; })
.attr("class", "node")
.each(function(d) { d.node = this; })
.on("mouseover", hovered(true))
.on("mouseout", hovered(false));
cell.append("rect")
.attr("id", function(d) { return "rect-" + d.id; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("fill", function(d) { return color[d.depth]; })
.attr("class",function(d){
if (d.depth < 2) return ;
return "cell " + d.data["タイプ"]
})
.call(bindTooltip)
;
total = d3.selectAll(".cell").nodes().length;
var count = fillCeckedCell();
output(count, total);
cell.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#rect-" + d.id + ""; });
var label = cell.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; });
label
.filter(function(d) { return d.children; })
.selectAll("tspan")
.data(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", function(d, i) { return i ? null : 4; })
.attr("y", 13)
.attr("fill", function(d){ return (d !="root") ? "black" : "white"; })
.text(function(d) { return (d !="root") ? d : "邦画10億円以上作品 2007-2016" ; });
label
.filter(function(d) { return !d.children; })
.selectAll("tspan")
.data(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", 4)
.attr("y", function(d, i) { return 13 + i * 10; })
.text(function(id) {var data = nested.get(id); return data["タイトル"]; });
});
function hovered(hover) {
return function(d) {
d3.selectAll(d.ancestors().map(function(d) { return d.node; }))
.classed("node--hover", hover)
.select("rect")
.attr("width", function(d) { return d.x1 - d.x0 - hover; })
.attr("height", function(d) { return d.y1 - d.y0 - hover; });
};
}
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("visibility", "hidden");
function bindTooltip(selector) {
selector.on("mouseover", function(d) {
if (!d.data["タイトル"]) return ;
d3.select(this).classed("selected", true);
tooltip.style("visibility", "visible");
})
.on("mouseout", function(d) {
selector.classed("selected", false);
tooltip.style("visibility", "hidden");
})
.on("mousemove", function(d){
var data = d.data;
var content = [
'<div class="tooltip_container">',
'<h4>', data['タイトル'], '</h4>',
'<h5>興収:', data['興収(単位:億円)'], '億円</h5>',
"</div>"].join("");
tooltip
.style("top", (d3.event.pageY-100)+"px")
.style("left",(d3.event.pageX-100)+"px")
.html(content);
});
}
d3.select("#form").on("change", function(){
d3.selectAll(".cell").attr("fill", "#fff");
var count = fillCeckedCell();
output(count, total);
});
function output(count, total) {
d3.select("#info").html('<span class="count">'+count + '</span><span class="unit">作品</span>/<span class="total">' + total + '</span> <span class="persent">' +format((count/total)*100)+'</span><span class="unit">%</span>')
}
function fillCeckedCell() {
var checked = d3.select("#form").selectAll("input:checked");
var count = 0;
checked.each(function(){
var selector = d3.selectAll("."+this.value).attr("fill", this.dataset.color);
count += selector.nodes().length;
});
return count;
}
</script>