スケールとは
スケールとは、入力された値を新しい範囲の数値に変換することです。これは、実はProcessingにおけるmap()と同じです。
map(value, low1, high1, low2, high2)は、valueの値を変更前の範囲から変更後の範囲内の値に変換してくれる便利な関数です。
https://r-dimension.xsrv.jp/classes_j/map_function/
これを踏まえて、d3のdomain(入力)とrange(出力)について学びます。このdomainとrangeのイメージは以下の図になります。
この例では、200を入力すると0を出力し、600を入力すると200を出力します。
2018年の都道府県別の人口をグラフにする場合、最小値が鳥取県の57万3441人、最大値が東京の1384万3403人です。
ということは、573,441〜13,843,403の値を例えば0〜500ピクセルの範囲に変換する必要が生じます。
d3でスケールを設定すると、この作業を自動的にscale関数一つでおこなってくれるのです。
それでは早速スケールを作成してみましょう。以下を記述してください。
var scale = d3.scaleLinear()
.domain([200, 1000])
.range([0, 400]);
このコードは、入力(domain)が200〜1000で、出力(range)が0〜400であることを示しています。
そして、chromeのコンソールで、scale(500)を記入してリターンキーを押します。
150という値が返ってきます。これでスケールが作成されました。
d3.max(), d3.min()
以下のコードを実行すると、配列中の最大値を返してくれます。同じように、最小値を返すd3.min()も使ってみてください。
//x座標用配列の値の最大値
var max_x = d3.max(dataset, function(d){
return d[0];
});
//y座標用配列の値の最大値
var max_y = d3.max(dataset, function(d){
return d[1];
});
console.log("max_x= " + max_x + ", max_y= " + max_y);
散布図のスケール関数を作成する
次に、スケール関数を作成します。
var xScale = d3.scaleLinear()
.domain([0, max_x])
.range([0, w]);
var yScale = d3.scaleLinear()
.domain([0, max_y])
.range([h, 0]);
consoleにxScale(500)などの色々な値を入れてみてください。スケールが機能していることが分かります。
次に、cx, cyを以下のように変更してみましょう。
attr("cx", function(d) {
return xScale(d[0]); //二次元配列の一つ目の値
})
.attr("cy", function(d) {
return yScale(d[1]); //二次元配列の二つ目の値
})
このことによって、画面の幅(w)、高さ(h)を変えると散布図も合わせて拡大縮小するようになりました。
同じように、テキストの位置にもスケールを適応してみましょう。
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
//半径(d[1]/10)+10ピクセル分だけ下に下げる
return yScale(d[1]) + d[1] / 10 + 10;
})
完全なコードは以下です。
<script type="text/javascript">
var w = 500;
var h = 300;
var dataset = [
[25, 50], [250, 100], [110, 80], [175, 240], [450, 280],
[130, 58], [190, 97], [154, 230], [60, 20], [171, 60]
];
//x座標用配列の値の最大値
var max_x = d3.max(dataset, function(d){
return d[0];
});
//y座標用配列の値の最大値
var max_y = d3.max(dataset, function(d){
return d[1];
});
//スケール関数を作成
var xScale = d3.scaleLinear()
.domain([0, max_x])
.range([0, w]);
var yScale = d3.scaleLinear()
.domain([0, max_y])
.range([h, 0]);
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]); //二次元配列の一つ目の値
})
.attr("cy", function(d) {
return yScale(d[1]); //二次元配列の二つ目の値
})
.attr("r", function(d){
return d[1]/10;
}); //半径
//ラベルを作成
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
//半径(d[1]/10)+10ピクセル分だけ下に下げる
return yScale(d[1]) + d[1] / 10 + 10;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "blue");
</script>
テキストにも反映されました。が、このままですと画面からはみ出してしまう場合があります。余白を追加しましょう。変数paddingを宣言し、余白を30にします。
var w = 500;
var h = 300;
var padding = 30;
スケールを以下のように書き換えましょう。xScaleのdomainは、右端がpaddingだけだとまだ切れてしまうので、x2にしています。
//スケール関数を作成
var xScale = d3.scaleLinear()
.domain([0, max_x])
.range([padding, w - padding * 2]);
var yScale = d3.scaleLinear()
.domain([0, max_y])
.range([h - padding, padding]);
完全なコードは以下です。
<script type="text/javascript">
var w = 500;
var h = 300;
var padding = 30;
var dataset = [
[25, 50], [250, 100], [110, 80], [175, 240], [450, 280],
[130, 58], [190, 97], [154, 230], [60, 20], [171, 60]
];
//x座標用配列の値の最大値
var max_x = d3.max(dataset, function(d){
return d[0];
});
//y座標用配列の値の最大値
var max_y = d3.max(dataset, function(d){
return d[1];
});
//スケール関数を作成
var xScale = d3.scaleLinear()
.domain([0, max_x])
.range([padding, w - padding * 2]);
var yScale = d3.scaleLinear()
.domain([0, max_y])
.range([h - padding, padding]);
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]); //二次元配列の一つ目の値
})
.attr("cy", function(d) {
return yScale(d[1]); //二次元配列の二つ目の値
})
.attr("r", function(d){
return d[1]/10;
}); //半径
//ラベルを作成
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
//半径(d[1]/10)+10ピクセル分だけ下に下げる
return yScale(d[1]) + d[1] / 10 + 10;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "blue");
</script>