d3

Scales

27 May 2014

D3.jsにはデータを変換する関数があります。

この関数は、入力値の領域を出力範囲に割り当てます(map)。

D3.jsのスケールは関数ですから、入力ドメインを出力範囲へマップするだけではなく、ドメインの中の数値を出力範囲内の値へ変換します。

数値の例

入力データ x が 0<= x <=10,000 の値で

これを 0<= x <=100 の範囲に変換したいとします。

以下のようにしたいと思います。

D3.jsのスケールでドメインをレンジにスケールダウンします。

D3.jsでドメインと呼ぶ範囲を取り、D3.jsでレンジと呼ぶ新しい範囲へと変換します。

こうする理由は、ここでのSVGビューポートの幅が100だからです。データをwinndow内に収める必要があるからです。

この変換をするために、このアルゴリズムに従います。

これは、元の幅の100単位が、新しい幅の1単位になるということです。

これを、リニア・スケーリング(linear scaling)と呼びます。

(y = mx + b , where b=0 and m = 1/100)

今回の例では、x = 10000 ならば、それを100 で割って y = 100 を得ます。

初期データが以下のとき

//Initial Data
var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

新しい範囲に変換するために、各値を手作業で100で割ることができます。

//New Data, scaled by 100 
var scaledByOneHundredData = [0, 10, 30, 20, 50, 40, 70, 60, 90, 80, 100];

これは辛いので

JavaScriptで書くと。

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];
 
var scaledByOneHundredData = [];
 
for (var i = 0; i < initialScaleData.length; i++) {
  scaledByOneHundredData[i] = initialScaleData[i] / 100;
}
 
scaledByOneHundredData;
//[0, 10, 30, 20, 50, 40, 70, 60, 90, 80, 100]

よくはなりましたが、まだまだ手作業が残っています。

いまだにコンピュータに、元の範囲(ドメイン)と新しい範囲(レンジ)を教えています。

そこで、データがきたら、自動で変換してほしいですね。

Linear Scale

D3.jsは、Scale Linear関数を使うことで、自動的に変換をしてくれます。

var identityScale = d3.scale.linear();

これは、初期ドメインが[0,1]で、レンジが[0,1]という 1:1 のマッピングを行う、線形スケールを生成します。

このデフォルトの線形スケールは、数字に対してidentity関数(入力値と同じ値を返す関数)のように働きます。

var identityScale = d3.scale.linear();

identityScale(1);
// 1

identityScale(2);
// 2

ドメインを指定することで、初期ドメインを書き換えます。

var domainOnlyScale = d3.scale.linear()
                              .domain([0,10000]);

レンジは変更されていないので、[0, 10000]のドメインは[0,1]のレンジへ変換されています。

var domainOnlyScale = d3.scale.linear()
                              .domain([0,10000]);

domainOnlyScale(1);
// 0.0001

domainOnlyScale(2);
// 0.0002

レンジを指定して、初期レンジを書き換えます。

var linearScale = d3.scale.linear()
                          .domain([0,10000])
                          .range([0,100]);

linearScale(1);
//0.01

linearScale(10);
// 0.1

linearScale(100);
// 1

linearScale(1000);
// 10

linearScale(10000);
// 100

JavaScriptのコードにすると

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];
 
var newScaledData = [];

var linearScale = d3.scale.linear()
                   .domain([0,10000])
                   .range([0,100]);

for (var i = 0; i < initialScaleData.length; i++) {
  newScaledData[i] = linearScale(initialScaleData[i]);
}

//newScaledData;
//[0, 10, 30, 20, 50, 40, 70, 60, 90, 80, 100]

D3.max

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 
9000, 8000, 10000];

初期データの中で、最大値は10000であるとわかっているので、

ドメインに[0,10000]と設定できました。

もし最大値がわからなかったとしたら

D3.jsには、配列から最大値を取得する関数 d3.max があります。

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

var maxInitialData = d3.max(initialScaleData);
// 10000

上のJavaScriptを書き換えると

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

var newScaledData = [];

var linearScale = d3.scale.linear()
                           .domain([0,d3.max(initialScaleData)])
                           .range([0,100]);

for (var i = 0; i < initialScaleData.length; i++) {
  newScaledData[i] = linearScale(initialScaleData[i]);
}

//newScaledData;
//[0, 10, 30, 20, 50, 40, 70, 60, 90, 80, 100]

これで最大値がわからなくても、max値が助けてくれます。

D3.min

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

最小値が 0 だと知っているので ドメインに[0,10000]と設定できます。

もし最小値がわからなかったとしたら

D3.jsには、配列から最小値を取得する関数 d3.min があります。

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

var minInitialData = d3.min(initialScaleData);
// 0

JavaScriptを書き換えると

var initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];

var newScaledData = [];
var minDataPoint = d3.min(initialScaleData);
var maxDataPoint = d3.max(initialScaleData);

var linearScale = d3.scale.linear()
                           .domain([minDataPoint,maxDataPoint])
                           .range([0,100]);

for (var i = 0; i < initialScaleData.length; i++) {
  newScaledData[i] = linearScale(initialScaleData[i]);
}

//newScaledData;
//[0, 10, 30, 20, 50, 40, 70, 60, 90, 80, 100]

最小値が分からなくても、この関数がマッピングを助けてくれます。

その他の D3.js Scales

Quantitative Scales

日付、時間、実数、 etc 連続したドメインをとります (上記のLinear scaleもその1つです)

 var q = d3.scale.quantize().domain([0, 1]).range(['a', 'b', 'c']);
 //q(0.3) === 'a', q(0.4) === 'b', q(0.6) === 'b', q(0.7) ==='c';
 //q.invertExtent('a') returns [0, 0.3333333333333333]

線形スケールを使うことで、SVG座標空間をデータに合わせてサイズ変更するのではなく、前もって定義したSVG座標空間へデータを合わせることができます。