|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
|
|
<style></style> |
|
|
|
<svg width="960" height="500"> |
|
<g id="chart"> |
|
<g id="legend" font-family="sans-serif" text-anchor="end"></g> |
|
<text id="cluster" font-size="64" text-anchor="middle"></text> |
|
</g> |
|
</svg> |
|
|
|
<script src="https://d3js.org/d3.v4.js"></script> |
|
|
|
<script> |
|
// 変化しない描画要素の初期化 |
|
var svg = d3.select("svg"), |
|
margin = { top: 20, right: 20, bottom: 30, left: 40 }, |
|
width = +svg.attr("width") - margin.left - margin.right, |
|
height = +svg.attr("height") - margin.top - margin.bottom; |
|
var chart = svg.select("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
var legend = chart.select("g"); |
|
var cluster = chart.select("text") |
|
.attr("x", width / 2) |
|
.attr("y", height / 2) |
|
.attr("fill", "black"); |
|
|
|
// 軸の変化しない部分を設定 |
|
var x0 = d3.scaleBand().rangeRound([0, width]); |
|
var x1 = d3.scaleBand() // .rangeRoundはx0.domainをやってみた後のグループ毎の幅を後でセットする |
|
.padding(0.1); // グループ内の各bar間の空白調整(単位は割合?) |
|
var y = d3.scaleLinear().rangeRound([height, 0]).domain([0, 100]); // 比較のためY軸のdomainは固定 |
|
|
|
// 描画単位が国かサービスかで、使うデータセットやスケール類を束ねておきます |
|
var bycountry = {}; |
|
var byservice = {}; |
|
var target = [bycountry, byservice]; |
|
|
|
// 色チャート |
|
bycountry.colors = d3.scaleOrdinal().range([ |
|
// Facebook, Google+, Twitter, LinkedIn, YouTube, USTREAM, Instagram, Tumblr, Pinterest, |
|
"#305097", "#db4a39", "#00aced", "#0077B5", "#cd201f", "#3388ff", "#bc2a8d", "#35465c", "#bd081c", |
|
// LINE, WhatsApp, 微博(Weibo), 微信(WeChat), 人人網(Renren), KakaoTalk, Other |
|
"#5ae628", "#075e54", "#df2029", "#7bb32e", "#2266b0", "#fcd411", "#6a737b" |
|
]); |
|
byservice.colors = d3.scaleOrdinal().range([ |
|
// Japan, USA, China, UK, Germany, India, Korea, Australia |
|
"Red", "blue", "yellow", "blue", "gold", "Saffron", "orange", "Green" |
|
]); |
|
|
|
// 年代のリスト |
|
var ages = {}; // total, 20-29,,,, |
|
// グループ内の項目名のリスト |
|
bycountry.group = []; // Facebook, Google+,,, |
|
byservice.group = {}; // Japan, USA,,, |
|
|
|
d3.csv("social.csv") |
|
// 各行の加工 |
|
.row(function (raw, i, columns) { |
|
ages[raw['Age']] = raw['Age']; |
|
byservice.group[raw['Country']] = raw['Country']; |
|
for (var j = 2, n = columns.length; j < n; ++j) |
|
raw[columns[j]] = +raw[columns[j]]; // 文字列値を可能なら数値に変換している |
|
raw['Group'] = raw['Country']; |
|
return raw; |
|
}) |
|
// 加工後のデータが揃ったら描画 |
|
.get(function (error, rows) { |
|
if (error) throw error; |
|
ages = Object.keys(ages); // 扱い易く素直なリストにしておく |
|
bycountry.group = rows.columns.slice(2); // Country,Age, までを捨てる |
|
byservice.group = Object.keys(byservice.group); // ages同様 |
|
// CSVの列構造ママ |
|
bycountry.data = rows; |
|
// Group, Age, Japan, USA,,,,, |
|
// Facebook, total, 35.3, 77.7,,,,, となる筈 |
|
byservice.data = Array.prototype.concat.apply([], bycountry.group.map((s) => { |
|
return ages.map((a) => { |
|
var temp = { "Group": s, "Age": a }; |
|
rows.filter((row, i) => row.Age == a).map((row) => temp[row.Country] = row[s]); |
|
return temp; |
|
}); |
|
})); |
|
// 今描画している単位(国orサービス)を保持するためのリスト |
|
bycountry.list = byservice.group.concat(); // Japan, USA,,, |
|
byservice.list = bycountry.group.concat(); // Facebook, Google+,,, |
|
// とりあえず描画 |
|
drawFrame(); |
|
drawLegend(); |
|
draw(); |
|
}); |
|
|
|
function draw() { |
|
var dataset = target[0]; |
|
var dset = dataset.data.filter((row, i) => row['Group'] == dataset.list[0]); |
|
|
|
// mapでAgeの凡例リストを作って、横軸の項目として設定して描画 |
|
x0.domain(dset.map((d) => d.Age)); |
|
chart.selectAll("#axisx").call(d3.axisBottom(x0)); |
|
// x0.bandwidth()で棒グラフのグループ毎の幅を取れる |
|
// その幅をさらにグループ内の項目数で均等割しているのではないか説 |
|
x1.domain(dataset.group).rangeRound([0, x0.bandwidth()]); |
|
|
|
// 棒グラフのグループにデータセットを束縛 |
|
var grps = chart.selectAll("#group") |
|
.data(dset, function (d) { return d.Age }); |
|
grps.enter() |
|
.append("g").attr("id", "group"); |
|
grps.exit().remove(); |
|
|
|
// グループの横位置を移動 |
|
chart.selectAll("#group") |
|
.attr("transform", function (d) { return "translate(" + x0(d.Age) + ",0)"; }); |
|
|
|
// グループ内の各barにデータを束縛 |
|
var bars = chart.selectAll("#group").selectAll("#bar") |
|
// 各グループ毎に棒を書くため[{key:"Facebook", value:2704659},{},,]というデータを作る |
|
.data(function (d) { return dataset.group.map(function (key) { return { key: key, value: d[key] }; }); }, function (d) { return d.key }); |
|
bars.enter() |
|
.append("rect").attr("id", "bar") |
|
// 以下は要素が追加された場合の初期値となる |
|
.attr("width", x1.bandwidth()) |
|
.attr("height", 0) |
|
.attr("x", function (d) { return x1(d.key); }) |
|
.attr("y", +svg.attr("height") - margin.top - margin.bottom); |
|
bars.exit().remove(); |
|
|
|
// 束縛したデータでbarを再描画 |
|
chart.selectAll("#bar") |
|
.transition().duration(1000) |
|
.attr("fill", function (d) { return dataset.colors(d.key); }) |
|
.attr("x", function (d) { return x1(d.key); }) |
|
.attr("y", function (d) { return y(d.value); }) |
|
.attr("width", x1.bandwidth()) |
|
.attr("height", function (d) { return height - y(d.value); }); |
|
|
|
// 説明テキストも再描画 |
|
cluster |
|
.transition().duration(500) |
|
.attr("opacity", 0.0) |
|
.transition().duration(500) |
|
.text(dataset.list[0]) |
|
.attr("opacity", 0.3); |
|
} |
|
|
|
function drawLegend() { |
|
var dataset = target[0]; |
|
legend.selectAll("g").remove(); |
|
var ls = legend.selectAll("g") |
|
.data(dataset.group.slice()) |
|
.enter() |
|
.append("g") |
|
.attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; }); |
|
ls.append("rect") |
|
.attr("x", width - 19) |
|
.attr("width", 19) |
|
.attr("height", 19) |
|
.attr("fill", dataset.colors); |
|
ls.append("text") |
|
.attr("x", width - 24) |
|
.attr("y", 9.5) |
|
.attr("dy", "0.32em") |
|
.text(function (d) { return d; }); |
|
} |
|
|
|
function drawFrame() { |
|
chart.append("g").attr("id", "axisx") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(0," + height + ")"); |
|
chart.append("g") |
|
.attr("class", "axis") |
|
.call(d3.axisLeft(y).ticks(null, "s")) |
|
.append("text") |
|
.attr("x", 2) |
|
.attr("y", y(y.ticks().pop()) + 0.5) |
|
.attr("dy", "0.32em") |
|
.attr("fill", "#000") |
|
.attr("font-weight", "bold") |
|
.attr("text-anchor", "start") |
|
.text("Utilization(%)"); |
|
} |
|
|
|
// カーソルキーを拾って描画対象を切り替え |
|
document.onkeydown = function (event) { |
|
if (event.keyCode == 39) { // right key |
|
target[0].list.push(target[0].list.shift()); |
|
draw(); |
|
} |
|
else if (event.keyCode == 37) { // left key |
|
target[0].list.unshift(target[0].list.pop()); |
|
draw(); |
|
} |
|
else if (event.keyCode == 38 || event.keyCode == 40) { // up or down key |
|
target.unshift(target.pop()); |
|
drawLegend(); |
|
draw(); |
|
} |
|
} |
|
|
|
</script> |