前回の続きです!引き続き、D3.jsでレーダーチャートを書きます。
レーダーチャートは書くことはできましたが、SVG領域の大きさが100×100であることが前提でコーディングされてます。
たとえば、前回作成したline関数はこんな感じでした。
var dataset = [ [5, 5, 2, 5, 5], [2, 1, 5, 2, 5] ]; var line = d3.svg.line() .x(function(d, i){ return 10 * d * Math.cos(2 * Math.PI / 5 * i - (Math.PI / 2)) + 50; }) .y(function(d, i){ return 10 * d * Math.sin(2 * Math.PI / 5 * i - (Math.PI / 2)) + 50; }) .interpolate('linear');
.xに渡してる関数が、半径 × cos(角度)
になっていて角度からレーダーチャートの頂点のx座標を求めてます。
10 * d
の部分が半径で、データの値dに応じて半径の大きさを変えています。10を掛けているのはdの値を5段階評価としているので、値に応じて半径が10,20,30,40,50になるようにするためです。
+50
の部分は、レーダーチャートの中心を(0, 0)から(50, 50)にずらしてます。
今日は、SVGの大きさに応じてレーダーチャートの大きさ(見た目上の大きさ)も動的に変化するようにしてみます。
あと、データの値の大きさ、データの要素数が変わった場合も動的に変化するようにします。
d3.scaleを使う
チュートリアルに書いてあったD3.jsのスケールが使えそう。データの値大きさと、表示上のピクセル値の大きさをマッピングするための機能が、D3.jsのスケール。
以下のように、d3.scale.linear()を使って関数を作る。domainに渡したデータ値の範囲を、rangeに渡したピクセル値の範囲にマッピングする関数が作れる。以下のrScala関数は引数が1の時は10を、引数が5の時は50を返してくれるようになる。
var rScale = d3.scale.linear() .domain([0, 5]) .range([0, 50]);
そして、半径の計算をしている箇所で、rScale関数を使うようにする。
var line = d3.svg.line() .x(function(d, i){ return rScale(d) * Math.cos(2 * Math.PI / 5 * i - (Math.PI / 2)) + 50; }) .y(function(d, i){ return rScale(d) * Math.sin(2 * Math.PI / 5 * i - (Math.PI / 2)) + 50; }) .interpolate('linear');
うん、問題なく動いてる。
さて、ここでrScale関数のrangeをSVGの大きさによって動的に変わるようにしてあげる。SVGの大きさは変数w,hで指定されているので、rangeの最大値をw/2にする。
var w = 100, h = 100; var rScale = d3.scale.linear() .domain([0, 5]) .range([0, w/2]);
うん、問題なく動いてる。じゃあ、SVGの大きさを100×100から200×200に変えてみる。
var w = 200,
h = 200;
お、大きくなったけど、ずれてる。中心点も動的にする必要があるなぁ。+50
の部分を+w/2
に書き換えてあげる。
var line = d3.svg.line() .x(function(d, i){ return rScale(d) * Math.cos(2 * Math.PI / 5 * i - (Math.PI / 2)) + w/2; }) .y(function(d, i){ return rScale(d) * Math.sin(2 * Math.PI / 5 * i - (Math.PI / 2)) + w/2; }) .interpolate('linear');
おー、できた!
rangeの方は動的になったけど、domainの方が5段階評価で固定になっちゃってる。こっちも動的にしよう。0から全データの最大値の範囲にすればいい。2次元配列の最大値を求めるにはd3.jsを使って以下のようにできる。求めたmaxを使ってdomainを指定する。
データ値として"8"を入れてみる。
var dataset = [ [5, 5, 2, 5, 8], [2, 1, 5, 2, 5] ]; var max = d3.max(d3.merge(dataset)); var rScale = d3.scale.linear() .domain([0, max]) .range([0, w/2]);
できてるできてる。
グリッド線を表すデータセットも固定になってるので、maxを使ってこんな感じにすればいいかな。
// var grid = [ // [1, 1, 1, 1, 1], // [2, 2, 2, 2, 2], // [3, 3, 3, 3, 3], // [4, 4, 4, 4, 4], // [5, 5, 5, 5, 5], //]; var grid = (function(){ var result = []; for(var i=1; i<=max; i++){ result.push([i, i, i, i, i]); } return result; })();
できてるできてる。
これで点数がデータの値が大きくなっても動的にレーダーチャートのメモリが増えるようになった。あとはデータの要素数が変わったときにレーダーチャートの頂点が動的に変わるようにする。
まずはデータの要素数を5から8に変更。要素数をparamCountにいれておいて、gridとline関数の定義時にparamCountを使うようにする。
var dataset = [ [5, 5, 2, 5, 8, 2, 3], [2, 1, 5, 2, 5, 6, 7] ], paramCount = dataset[0].length, grid = (function(){ var result = []; for(var i=1; i<=max; i++){ var arr = []; for (var j=0; j<paramCount; j++){ arr.push(i); } result.push(arr); } return result; })(), line = d3.svg.line() .x(function(d, i){ return rScale(d) * Math.cos(2 * Math.PI / paramCount * i - (Math.PI / 2)) + w/2; }) .y(function(d, i){ return rScale(d) * Math.sin(2 * Math.PI / paramCount * i - (Math.PI / 2)) + w/2; }) .interpolate('linear');
うん、できた!
おしまい
これで、svg領域のサイズ、データの値の大きさ、データの要素数、それぞれの値が変わった場合にレーダーチャートの形も動的に変わるようにできました。
次は、各頂点にラベルをふるのと、チャート毎に色分けできるようにしたいと思います。
ソースコード全体は以下です。
var w = 200, h = 200, svg = d3.select('body') .append('svg') .attr('width', w) .attr('height', h), dataset = [ [5, 5, 2, 5, 8, 2, 3], [2, 1, 5, 2, 5, 6, 7] ], paramCount = dataset[0].length, max = d3.max(d3.merge(dataset)), rScale = d3.scale.linear() .domain([0, max]) .range([0, w/2]), grid = (function(){ var result = []; for(var i=1; i<=max; i++){ var arr = []; for (var j=0; j<paramCount; j++){ arr.push(i); } result.push(arr); } return result; })(), line = d3.svg.line() .x(function(d, i){ return rScale(d) * Math.cos(2 * Math.PI / paramCount * i - (Math.PI / 2)) + w/2; }) .y(function(d, i){ return rScale(d) * Math.sin(2 * Math.PI / paramCount * i - (Math.PI / 2)) + w/2; }) .interpolate('linear'); svg.selectAll('path') .data(dataset) .enter() .append('path') .attr('d', function(d){ return line(d)+"z"; }) .attr("stroke", "black") .attr("stroke-width", 2) .attr('fill', 'none'); svg.selectAll("path.grid") .data(grid) .enter() .append("path") .attr("d", function(d,i){ return line(d)+"z"; }) .attr("stroke", "black") .attr("stroke-dasharray", "2") .attr('fill', 'none');