D3.v4入門としてやってみた。
総務省の情報通信白書(平成28年版)より、SNS等のサービス利用度を可視化。
若者のFacebook離れというのはデマですね。。
カーソルキーを叩くとデータセットが切り替わります。
参考にさせて頂いたサイト
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.js