データの視覚化: 第 2 回 D3 のコンポーネント・レイアウトを使用する

さまざまな配置でコンポーネントを描画するためのグラフィック計算について学ぶ

この全 2 回からなる連載記事では、SVG (Scalable Vector Graphics) とオープンソースの D3 JavaScript ライブラリーを使用してデータを視覚化する方法を学びます。ビジネスでデータの大きさを把握する上では、形状、色、レイアウトが大いに役立つ場合があります。今回の記事では、D3 による計算と独自の計算の両方を使用して、キャンバス上にグラフィック・コンポーネントを配置してデータを表現するさまざまな方法を実演します。

Bilal Siddiqui , Freelance consultant, XML4Java

Bilal Siddiqui は、電子工学エンジニア、XML コンサルタント、テクノロジー・エバンジェリスト、そして活発に著作活動を行っている技術書の著者であり、e-business の簡易化を専門とする会社、XML4Java.com の設立者でもあります。1995年にパキスタンのラホールにある University of Engineering and Technology を卒業した後、産業用制御システムを対象としたソフトウェア・ソリューションの設計を始めました。その後、XML に転向し、Web ベースや WAP ベースの XML 処理ツール、サーバー・サイドの構文解析ソリューション、そしてサービス・アプリケーションを作成しました。2006年からは、Java や XML をベースとしたオープンソースのツールおよびソリューションに専門的に取り組んでいます。オープンソース・ツールの熱烈な支持者である彼は、オープンソース・ツールをベースとしたソリューションを設計するだけでなく、ラホールの大学でオープンソース技術の使用方法について、ソフトウェアおよび IT 担当者のトレーニングも行っています。彼は、『JasperReports 3.6 Development Cookbook』(Packt Publishing、2010年) の著者です。



2013年 9月 12日

この全 2 回からなる連載記事の第 1 回では、ソーシャル・メディアのブラウジング・データを視覚化する基本的な例をいくつか用いて、SVG と D3 が連携する仕組みを概説しました。この第 2 回では、SVG による描画の中でグラフィック・コンポーネントをさまざまな配置 (レイアウト) で使用するためのステップについて説明します。D3 による強力なグラフィック計算を使用して SVG キャンバス上にコンポーネントを配置する方法、そして D3 のレイアウトに独自のグラフィック操作を結合する方法を学んでください。さらにこの記事では、視覚化のデータ・フォーマットとして JSON (JavaScript Object Notation) を使用する方法についても探ります。最後に、レイアウトを組み合わせて使用することで、1 つの SVG キャンバスにさまざまなグラフィック・コンポーネントを配置できる様子を紹介して記事を締めくくります。

この記事で説明する概念およびサンプルは、第 1 回で説明した概念およびサンプルに基づいているので、この先を読み進める前に必ず第 1 回を読むか、見直してください。第 2 回を読むときには、第 1 回に記載されている画像を参照できるように、両方の記事をブラウザーで開いて並べておくと役に立つかもしれません。第 2 回のサンプル・コードを入手するには、「ダウンロード」セクションを参照してください。

D3 のグラフィック・レイアウトの概要

まず、第 1 回で D3 の機能に関して学んだ内容に説明を加えることから始めます。

第 1 回に記載されている図 1 と図 2 の唯一の違いは円の配置方法であること、そしてこの 2 つの図に対応する JavaScript コード (第 1 回のリスト 2 とリスト 4 に記載) の transform 属性値が、各円の中心の相対位置を計算することを思い出してください。

個々のコンポーネントの相対位置を決定するグラフィック計算は、D3 の用語では「レイアウト」と呼ばれます。D3 には、強力で再利用可能な各種のレイアウトが用意されています。第 1 回の弧と弦の配置も、その 1 つです。D3 のレイアウトを単独で使用する方法、そして独自に作成したグラフィック計算と組み合わせて使用する方法を両方とも知っておくと役立ちます。

図 1 に示す D3 の「パック・レイアウト」は、複数の円を一回り大きな円の中に包含したものです (第 1 回と同じく、内部の円は第 1 週に各ソーシャル・メディア・リソースのページが表示された回数を表しています)。今回の記事では、パック・レイアウトを私が独自に作成した計算と併せて使用する方法を説明します。

図 1. 大きな円の中に複数の円を表示するパック・レイアウト
D3 のパック・レイアウト: 大きな円に包含された複数の円

図 1 の内部の円は、第 1 回で生成した円と同じですが、配置は異なっています。この違いは、D3 のパック・レイアウトが行うグラフィック計算によるものです。

外側の円がなければ、図 1 のレイアウトは図 2 のような表示になります。

図 2. 外側の円がないパック・レイアウト
外側の円がないパック・レイアウト

第 1 回の図 1 では私独自の単純なグラフィック計算に基づく単純なレイアウトを使用していますが、上記の図 2 では、D3 のパック・レイアウトを使用しています。この 2 つのレイアウトが行うグラフィック計算を組み合わせると、複数の週の人気度データを視覚化することができます (図 3 を参照)。

図 3. 1 つのグラフに示された第 1 週から第 3 週の人気度データ
1 つのグラフに示された第 1 週から第 3 週の人気度データ

また上記図 3 と、第 1 回図 3 に示されているネストされた円の配置を組み合わせることにより、複数の週の人気度データとユーザーの交流データを 1 つのキャンバス上で同時に表現することもできます (図 4 を参照)。

図 4. 複数の週の人気度データとユーザーの交流データを表示する複数のレイアウトの組み合わせ
複数の週の人気度データとユーザーの交流データを表示する複数のレイアウトの組み合わせ

ここからは、図 1 から図 4 のそれぞれを表示するための SVG コードと D3 ベースの JavaScript コードについて説明します。


円のパックを描画する

リスト 1 に、図 1 に示されている円のパックを描画する SVG コードを記載します。

リスト 1. 図 1 の円のパックを描画する SVG コード
<?xml version=";1.0"; standalone=";no";?>
<!DOCTYPE svg PUBLIC ";-//W3C//DTD SVG 1.1//EN"; 
";http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>

<svg xmlns=";http://www.w3.org/2000/svg"; version=";1.1";
width=";1000"; height=";1000";>

<g>
   <circle 
      r=";250"; 
      style=";fill: #da70d6;";
      transform=";translate(250,250)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=";translate(250,250)";>
         26733
   </text>
</g>

<g>
   <circle 
      r=";91.28916405756645";
      style=";fill: #0000ff;";
      transform=
       ";translate(172.869706621408,159.71405581800542)";>
   </circle>
   <text
      x=";-10"; fill=";grey"; 
      transform=
       ";translate(172.869706621408,159.71405581800542)";>
            7057
   </text></g>

<g>
   <circle
      r=";94.00415374552455";
      style=";fill: #ffd700;";
      transform=
        ";translate(120.90301328980084,337.57095449403647)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
       ";translate(120.90301328980084,337.57095449403647)";>
            7483
   </text>
</g>

<g>
   <circle
      r=";66.53756320431968";
      style=";fill: #008000;";
      transform=
        ";translate(271.77788076862765,282.7036851574789)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
        ";translate(271.77788076862765,282.7036851574789)";>
            3749
   </text>
</g>

<g>
   <circle
      r=";67.39284824138821";
      style=";fill: #ff0000;";
      transform=
        ";translate(405.70829221433553,282.7036851574789)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
        ";translate(405.70829221433553,282.7036851574789)";>
            3846
   </text>
</g>

<g>
   <circle
      r=";73.68747158608183";
      style=";fill: #800000;"; 
      transform=
        ";translate(337.8448727920879,159.01774033373852)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
        ";translate(337.8448727920879,159.01774033373852)";>
            4598
   </text>
</g>

</svg>

ご覧のように、リスト 1 のルート <svg> タグには 6 つの <g> 子タグが含まれていて、子タグのそれぞれには <circle> タグと <text> タグのペアが含まれています。外側の大きな円を描画する最初の <g> タグは、D3 のパック・レイアウトについて理解してもらえるようにリストに含めているだけです。このサンプル・アプリケーションには外側の円は必要ないので、円が見えなくなるように、色を da70d6 (オーキッドと呼ばれる色) から fffff (白) に変更します。これにより、図 1 ではなく図 2 の表示になります。残りの 5 つの <circle> タグは、図 2 でソーシャル・リソースの人気度を表す 5 つの円のうちの 1 つをそれぞれ描画します。

5 つの <circle> タグについて注目すべき点は 2 つあります。

  • <circle> タグの r 属性 (各円の半径) は、その円のソーシャル・リソースの人気度を表します (リソースの人気が高いほど、円は大きくなります)。
  • transform 属性の translate(x,y) 値は、パック内での各円の位置を指定します。

以上の rxy の値によって、各円のサイズと位置が完全に決まります。D3 のパック・レイアウトは、この 3 つの属性を自動的に計算します。リスト 2 に、D3 を使用してグラフィック計算を行って、リスト 1 の SVG を生成する JavaScript コードを記載します。

リスト 2. 円のパック・レイアウトを生成する D3 ベースの JavaScript コード
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
 -->
<script src="d3.v3.js"></script>
<!--Save d3.v3.js in the same folder-->
<script>

var views = [
  [7057, 7483, 3749, 3846, 4598],
  [ 2371, 7397, 4589, 2861, 8249],
  [ 5972, 5672, 9152, 9725, 8983],
  [ 9763,  8462,  9782, 1953, 5182],
  [ 9567,  1571,  2895, 2783, 1874],
  [ 2371, 7397, 4589, 2861, 8249]
		];
var width = 1000, height = 1000;
var colors = [
 "white",//"orchid", if you want to see the outer bigger circle.
            "blue",
            "gold",
            "green", 
            "red", 
            "maroon"
		];
var week = 0;

//Step 1: Make JSON representation of popularity data.
var viewsJSON = getJSONForOneWeekOfPopularityData(0);

//Step 2: Pass on JSON to pack layout and get graphical 
//calculations in return.
var pack = d3.layout.pack()
    .size([500, 500])
    .value(function(d) { return d.popularity; });
var packCalculations = pack.nodes(viewsJSON);

//Step 3: Use calculations to generate SVG.
var svg = d3.select("body").append("svg")

.attr("width", width)
   .attr("height", height)

   .selectAll("g").data(packCalculations).enter();
var g = svg.append("g");
g.append("circle")
  .attr("r", function(d){return d.r})
  .style("fill", function(d, i){return colors[i%6];})
  .attr("transform", function(d,i){return "translate(" + d.x + "," + d.y + ")"});
g.append("text")
  .attr("x", -10)
  .attr("fill", "grey")
  .text(function(d){return d.value})
  .attr("transform", function(d,i){return "translate(" + d.x + "," + d.y + ")"});

function getJSONForOneWeekOfPopularityData(week){
var viewsJSONString = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0;
           count<5;
           count++   ){
  viewsJSONString += 
    "{ \"name\": \"" + colors[count+1] + " \", \"popularity\": " 
    + views[week][count] + " }   ";

  if (count<4) viewsJSONString += ",";

  }
  viewsJSONString += "]}"
  return JSON.parse(viewsJSONString);
}
</script>
</body>

リスト 2 では、以下の 3 つのステップが行われています。

  1. 人気度データを、D3 のパック・レイアウトがグラフィック計算を行うために使用できる JSON 表現にします。
  2. JSON オブジェクトをパック・レイアウトに渡して、グラフィック計算を実行させた結果を取得します。
  3. 計算を使用して、必要な SVG コードを生成します。

この後、以上のステップのそれぞれについて説明します。

ステップ 1: JSON の処理

パック・レイアウトは、階層的なレイアウトです。つまり、大きな (親) 円の内部に小さな (子) 円を含めるという形で階層データを表現します (D3 は階層レベルの数に制限を設けていません。親、親の親、さらに親の親の親といった具合に任意の数のレベルにすることができます)。サンプルの人気度データとユーザーの交流データには 2 つのレベルしかありません。親レベルは週の番号で、子レベルは週ごとの人気度データとユーザーの交流データです。

パック・レイアウトは JavaScript の配列を直接処理しません。代わりに、データの JSON 表現を入力として取ります。JSON データ・フォーマットは階層データを上手く表現できるため、最近のブラウザーでは直接サポートされています。

リスト 3 に、人気度データの JSON 表現と、同じデータを配列で表現した場合との比較を示します。

リスト 3. 第 1 週から第 3 週の人気度データの配列表現と JSON 表現
//array representation
var views = [
  [7057, 7483, 3749, 3846, 4598],
  [ 2371, 7397, 4589, 2861, 8249],
  [ 5972, 5672, 9152, 9725, 8983]
                   ];

//JSON representation
var viewsJSON = 
{
  "name": "PopularityData",
  "children": [
  {
    "name": "week1",

    "children": [
      {"name": "blue", "popularity": 7057},
      {"name": "gold", "popularity": 7483},
      {"name": "green", "popularity": 3749},
      {"name": "red", "popularity": 3846},
      {"name": "maroon", "popularity": 4598}
    ]
  },
  {
    "name": "week2",
    "children": [
      {"name": "blue", "popularity": 2371},
      {"name": "gold", "popularity": 7397},
      {"name": "green", "popularity": 4589},
      {"name": "red", "popularity": 2861},
      {"name": "maroon", "popularity": 8249}
    ]
  }
  { 
    "name": "week3",
    "children": [
      {"name": "blue", "popularity": 5972},
      {"name": "gold", "popularity": 5672},
      {"name": "green", "popularity": 9152},
      {"name": "red", "popularity": 9725},
      {"name": "maroon", "popularity": 8983}
    ]
  }
]};

JSON 表現では名前と値のペアが使用されることに注目してください。データ・オブジェクト全体の名前は PopularityData です。PopularityData には 3 つの子があり、それぞれ Week1Week2Week3 という名前が付けられています。さらに、週の子のそれぞれには、ソーシャル・リソースにちなんだ名前が付けられた 5 つの子があります (第 1 回で、ソーシャル・リソースの名前は色の名前で表すようにしたことを思い出してください)。これらのオブジェクトはすべて「ノード」と呼ばれます。そのうち、子を持たないノード (つまり、ツリー階層の終わりにあるノード) は、「リーフ・ノード」と呼ばれます。人気度データは、各リーフ・ノードの popularity 属性の値です。

リスト 4 に、1 週間の人気度データの JSON オブジェクトを作成する方法を示します。

リスト 4. JSON フォーマットでの 1 週間の人気度データ
var viewsJSON = 
{

    "name": "week1",
    "children": [
      {"name": "blue", "popularity": 7057},
      {"name": "gold", "popularity": 7483},
      {"name": "green", "popularity": 3749},
      {"name": "red", "popularity": 3846},
      {"name": "maroon", "popularity": 4598}
  ]
};

リスト 5 に、リスト 3 に示されている JSON オブジェクト内で人気度データにユーザーの交流データを追加する方法を示します。

リスト 5. JSON フォーマットで表された、第 1 週から第 3 週の人気度データとユーザーの交流データ
var viewsJSON = 
{
  "name": "PopularityData",
  "children": [
  {
    "name": "week1",
    "children": [
      {"name": "blue", "popularity": 7057, 
                       "user-interaction": 2052},
      {"name": "gold", "popularity": 7483},
                       "user-interaction": 2089},
      {"name": "green", "popularity": 3749},
                       "user-interaction": 1586},
      {"name": "red", "popularity": 3846},
                       "user-interaction": 1426},
      {"name": "maroon", "popularity": 4598}
                       "user-interaction": 2632},
    ]
  },
  {
    "name": "week2",
    "children": [
      {"name": "blue", "popularity": 2371},
                       "user-interaction": 2071},
      {"name": "gold", "popularity": 7397},
                       "user-interaction": 2190},
      {"name": "green", "popularity": 4589},
                       "user-interaction": 7214},
      {"name": "red", "popularity": 2861},
                       "user-interaction": 3782},
      {"name": "maroon", "popularity": 8249}
                       "user-interaction": 2721},
    ]
  }
  {
    "name": "week3",
    "children": [
      {"name": "blue", "popularity": 5972},
                       "user-interaction": 3076},
      {"name": "gold", "popularity": 5672},
                       "user-interaction": 3190},
      {"name": "green", "popularity": 9152},
                       "user-interaction": 4532},
      {"name": "red", "popularity": 9725},
                       "user-interaction": 3825},
      {"name": "maroon", "popularity": 8983}
                       "user-interaction": 4831},
    ]
  }
]};

リスト 5 からわかるように、ユーザーの交流データを追加する上で必要なことは、user-interaction という名前の属性を各リーフ・ノードに追加することのみです。

JavaScript 内で JSON データを使用するには、いくつかの方法があります。1 つの方法は、JSON データを JavaScript ファイルに直接含める方法です。また、<script> タグを使用して、JSON ファイル (.json 拡張子が付いたファイル) をページに関連付けるという方法もあります。あるいは、データを別のフォーマット (JavaScript 配列など) にして、実行時に動的に JSON に変換することも可能です。

外部 JSON ファイルを JavaScript ページに関連付けると、セキュリティー上の問題が生じる可能性があります。一方で、配列を使用すると、JavaScript ファイルをサーバー・サイドで組み立てるときに、データを格納する簡単かつ明快な手段となります。こうしたことから、私は 3 番目の選択肢として、データを JavaScript 配列に格納し、同じ JavaScript ファイル内で配列から JSON への変換ロジックを使用するという方法を選択しました。この方法の場合、データの JSON 表現が必要になった時点で、配列から JSON への変換ロジックを呼び出すことができます。

お察しのとおり、図 1 のレイアウトを描画するには、リスト 4 のようにフォーマット設定された各週の人気度データの JSON 表現が必要となります。このフォーマットで JSON オブジェクトを生成するのが、getJSONForOneWeekOfPopularityData() という名前の単純な JavaScript 関数です (リスト 6 を参照)。

リスト 6. JavaScript 配列からの JSON オブジェクトの生成
function getJSONForOneWeekOfPopularityData(week){

//Step 1: Author a string to JSON format.
var viewsJSONString = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0;
           count<5;
           count++   ){
  viewsJSONString += 
    "{ \"name\": \"" + colors[count+1] + " \", \"popularity\": " 
    + views[week][count] + " }   ";

  if (count<4) viewsJSONString += ",";

  }
  viewsJSONString += "]}"

//Step 2: Parse the string to create a JSON object.
  return JSON.parse(viewsJSONString);
}
</script>
</body>

リスト 6 に示されているように、JSON オブジェクトは、単純な 2 つのステップからなる変換プロセスで生成されます。このプロセスではまず、生成する必要のある JSON フォーマットに正確に従った JavaScript の String 表現を作成します。ストリング表現を作成するときには、その表現がリスト 4 とまったく同じになるように、ソーシャル・リソースの実際の名前を使用します。そして 2 番目のステップで、JSON.parse() メソッドを使用してストリングを JSON オブジェクトに変換します。最近のほとんどのブラザーは JSON に関連する機能をサポートしているため、JSON.parse() 関数を直接呼び出すことができます。この記事のコードは、Chrome のバージョン 26.0.1410.64 m で試しました。アプリケーションでターゲットとしているブラウザーが古いものである場合は、<script> タグを使用して、ページ内で https://github.com/douglascrockford/JSON-js/blob/master/json2.js ライブラリー (JSON 機能を実装するライブラリー) を読み込むことができます。

ステップ 2: パック・レイアウトによるグラフィック計算

適切な JSON オブジェクトが準備できたので、このオブジェクトを D3 のパック・レイアウトに渡せば、必要なグラフィック計算を実行することができます。リスト 7 (便宜上、リスト 2 のステップ 2 を繰り返しています) を見ると、パック・レイアウトからは簡単にグラフィック計算を取得できることがわかります。

リスト 7. D3 を使用して実行する、円のパック内での週単位の人気度データのグラフィック計算
//Step 2: Pass on JSON to pack layout and get graphical 
//calculations in return.
var pack = d3.layout.pack()
    .size([500, 500])
    .value(function(d) { return d.popularity; });
var packCalculations = pack.nodes(viewsJSON);

このように、d3.layout.pack() オブジェクトを作成した後、size() 関数を呼び出してパック・レイアウトのサイズを設定すれば良いのです。size() 関数は、長さと幅を引数として取ります。この長さと幅が設定するサイズの範囲内で、円のパックが描画されます。個々の円のサイズは、D3 が内部でレイアウトのサイズに応じて拡大/縮小します。

続いて、パック・レイアウト・オブジェクトに対して value() 関数を呼び出し、独自の関数を値として書き込みます。この関数は、各 JSON ノードの値を返します。グラフィック計算を行っている間、D3 は内部でこの関数を各ノードに対して 1 回呼び出し、そのノードを d パラメーターとして渡します。リスト 7 に示されているように、ノードの値として d.popularity を返すことによって、D3 に人気度データに基づくグラフィック計算を行うように指示します。その結果、パック内の各円は、ノードの人気度データに応じたサイズに設定されます。

これで、パック・レイアウト・オブジェクトが JSON オブジェクトを取得して計算を実行する準備が整いました。次は、JSON オブジェクトを渡してパック・レイアウト・オブジェクトの nodes() 関数を呼び出します。

nodes() 関数は、人気度を表す円をパック・レイアウト内に収容するためのグラフィック計算のすべてを行います。これらの計算の結果は、1 つの円のサイズと描画位置を表すノードからなる配列の形になります。このように、ノードの配列全体に人気度の円のパック全体を描画するためのすべてのデータが含まれます。

簡単に言うと、まず JavaScript 配列を JSON に変換し、その JSON を D3 パック・レイアウトに渡します。すると、パック・レイアウトがグラフィック計算を実行して、計算を別の配列 (今度はノードの配列) に格納します。リスト 7 でその別の配列に該当するのは packCalculations という名前の配列です。

packCalculations 配列に含まれるノードごとに、D3 は rxy 属性を含む複数の属性を指定します。次に説明するのが、これらの属性を使用して、リスト 1 の SVG コードを生成する方法です。

ステップ 3: パック・レイアウトの計算を使用して SVG を生成する

ステップ 2 が完了すると、packCalculations 配列に、円のパックを描画するために必要なすべてのグラフィック・データが揃った状態になります。ステップ 3 (リスト 8 に再現) では、計算を使用して SVG を生成します。

リスト 8. グラフィック計算を使用した円の描画
//Step 3: Use calculations to generate SVG.
var svg = d3.select("body").append("svg")

.attr("width", width)
   .attr("height", height)
   .selectAll("g").data(packCalculations).enter();
var g = svg.append("g");
g.append("circle")
  .attr("r", function(d){return d.r})
  .style("fill", function(d, i){return colors[i%6];})
  .attr("transform", function(d,i)
       {return "translate(" + d.x + "," + d.y + ")"});
g.append("text")
  .attr("x", -10)
  .attr("fill", "grey")
  .text(function(d){return d.value})
  .attr("transform", function(d,i)
         {return "translate(" + d.x + "," + d.y + ")"});

リスト 8 に記載されている内容の大部分は、第 1 回で記載したものです。唯一の違いは、この場合のデータは packCalculations 配列から取得されるという点です。配列から各ノードを取得して円を 1 つずつ描画しなければならないわけではないので心配しないでください。packCalculations 配列を .data(packCalculations) D3 関数に渡すだけで、この魔法のような関数が作業のほとんどを処理してくれます。

リスト 8 では d.rd.x、および d.y 属性が使用されていることに注目してください。各円のサイズと位置を決定するには、これらの rxy 属性が必要です。d.r 属性を使用して円の r 属性 (半径) を生成し、d.x 属性と d.y 属性を使用して、各円の位置を指定する transform 属性の値を生成します。

リスト 2 の JavaScript を実行すると、図 2 に示されている画像が表示されます。


外部計算とパック・レイアウトの結合

ここからは、図 3 のグラフィックを描画するために、パック・レイアウトに独自のグラフィック計算を組み合わせる方法を説明します。図 3 では円のパック・レイアウトを 3 回 (週ごとに 1 回) 使用して、5 つの円のセットを 3 つ表示しています。ここで、各セットは 1 週間分の人気度データを表します。

リスト 9 に図 3 の SVG コードを記載します。

リスト 9. 第 1 週から第 3 週の人気度データを表示する SVG コード
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="1000" height="1000">

<g transform="translate(0,143)">
  <text x="60">Week 1</text>
  <!-- resource-popularity tags omitted.-->
</g>

<g transform="translate(320,143)">
  <text x="60">Week 2</text>
  <!-- resource-popularity tags omitted.-->
</g>

<g transform="translate(640,143)">
  <text x="60">Week 3</text>
  <!-- resource-popularity tags omitted.-->
</g>

</svg>

リスト 9 には、ルート <svg> タグの 3 つの <g> 子タグが示されています。これらの 3 つの <g> タグは、それぞれに 1 週間分のデータを表すことから、「週ラッパー」タグと呼ぶことにします。

それぞれの週ラッパー・タグに、6 つの <g> 子タグがあります (1 つの大きな円と、人気度を表す 5 つの円)。これらのタグについては、「リソース人気度」タグと呼ぶことにします。リソース人気度タグがどのようなタグであるかは、リスト 1 からおわかりかと思うので、このリストからはリソース人気度タグを省略しました。

ここで、週ラッパー・タグの transform 属性に注目してください。これらの属性の値は、最初の円のパックをポイント (0, 143) に、2 番目のパックをポイント (320, 143) に、3 番目のパックをポイント (640, 143) にいずれもセンタリングして配置します。この配置は、私が円のパック全体をラップするために作成した単純なグラフィック計算による結果です。その方法をこれから説明します。

リスト 9 の SVG コードを生成する JavaScript

リスト 10 の JavaScript コードは、D3 のパック・レイアウトに私のグラフィック計算のいくつかを結合して、リスト 9 に記載されている SVG を生成します。

リスト 10. パック・レイアウトに追加のグラフィック計算を結合する JavaScript
<!DOCTYPE html>

<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
 -->
<script src="d3.v3.js"></script>
<script>

var views = [
  [7057, 7483, 3749, 3846, 4598],
  [ 2371, 7397, 4589, 2861, 8249],
  [ 5972, 5672, 9152, 9725, 8983],
		];


var colors = [
  "white",
  "blue",
  "gold",
  "green", 
  "red", 
  "maroon"   ];

var width = 1500, height = 1000;
var widthOfOnePack = 300, heightOfOnePack = 300;
var spaceBetweenPacks = 20;
var packCalculations = [];
for (var count=0; count<3; count++){
  var pack = d3.layout.pack()
              .size([widthOfOnePack, heightOfOnePack])
              .value(function(d) { 
                 return d.popularity; });
  packCalculations[count] = 
     pack.nodes(
       getJSONForOneWeekOfPopularityData(count)
               );
}
var svg = d3.select("body").append("svg")
   .attr("width", width)
   .attr("height", height);
var g1 = svg.selectAll("g")
          .data(packCalculations).enter().append("g")
     .attr("transform", function(d,i){
             return "translate(" + 
            (widthOfOnePack + spaceBetweenPacks )*i + 
            "," + height / 7 + ")" });
g1.append("text")
.text(function(d, i){return "Week " + (i+1)})
  .attr("x", 60);

var g2 = g1.selectAll("g")
   .data(function(d){return d;})
   .enter()
   .append("g");

g2.append("circle")
  .attr("r", function(d, i){return d.r})
  .style("fill", function(d, i){return colors[i%6];})
  .attr("transform", function(d,i){
         return "translate(" + d.x + "," + d.y + ")"});

g2.append("text")
  .attr("x", -10)
  .attr("fill", "grey")
  .text(function(d, i){return d.value})
  .attr("transform", function(d,i){
        return "translate(" + d.x + "," + d.y + ")"});

function getJSONForOneWeekOfPopularityData(week){
var viewsJSONString = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0;
           count<5;
           count++   ){
  viewsJSONString += 
    "{ \"name\": \"" + colors[count+1] + " \", \"popularity\": " 
    + views[week][count] + " }   ";

  if (count<4) viewsJSONString += ",";

  }
  viewsJSONString += "]}"
  return JSON.parse(viewsJSONString);
}

</script>
</body>

リスト 10 のコードをリスト 2 と比較しやすくするために、上記のコードでは packCalculations および transform の拡張部分を強調表示しました。

リスト 2 では packCalculations を単純な変数として宣言していますが、リスト 10 では配列として宣言しています。その理由は、リスト 10 で処理する 3 つの円のパックには、それぞれ独自のグラフィック計算が使用されるためです。

packCalculations 配列を宣言した後、単純なループを実行し、そのループ内で D3 のパック・レイアウトを使用してパック内での各円の位置を指定するための計算を実行します。D3 は週ごとのノードの配列を返します。私は D3 の各計算セットを packCalculations 配列に格納しているので、これで、packCalculations はノードの配列となります。

ここで興味深い点は、D3 のすべての計算は、1 つのパックだけを処理しているかのように、他のパックとは無関係に 1 つのパックに対して実行されることです。これらの計算では、他のパックが同じ SVG キャンバス上にある可能性は考慮されません。けれども、transform 属性が週ラッパー・タグの位置を計算すると、週ラッパーに含まれるすべてのものが、このラッパーを基準とした相対位置に配置されます。したがって、円の複数のパックを簡単に配置することができます。

今度は、リスト 10 に太字で示されている週ラッパー・タグの transform 属性値の計算に目を向けてください。ここでは、1 つのパックの幅に多少の余白を加えた値に、インデックス i を乗算しています。この調整により、最初の週ラッパー (i=0) は水平方向にずらすことなく配置され、2 番目の週ラッパー (i=1) は、最初の週ラッパーの幅と多少の余白の分だけ水平方向にずらして配置されることになります。同じように、3 番目の週ラッパーは、最初の 2 つの週ラッパーの幅と追加の余白の分だけずらして配置されます。

リスト 10 の残りの部分はリスト 2 と同じく、各パックの個々の円を描画します。ここでは明らかに以下のことが可能です。

  • <g> タグの親子の配列を設計して、レイアウトを操作すること。
  • <g> タグの transform 属性値を使用して、個々のコンポーネントまたはコンポーネントのレイアウト全体を目的の場所に配置すること。

ネストされた円のパック・レイアウト

次に、図 3 のレイアウト内にユーザーの交流データを追加するために、リスト 10 に加えることができる興味深い拡張について説明します。この拡張により、図 4 に示されているネストされた円のパックの形になります。

リスト 11 に、図 4 を描画するための JavaScript を記載します。

リスト 11. 図 4 を描画するための JavaScript
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
 -->
<script src="d3.v3.js"></script>

<script>

var viewsAndInteraction = [
[ [7057, 2052], [7483, 2089], [3749, 1586], [3846, 1426], [4598, 2632] ],
[ [5972, 2071], [5672, 2190], [9152, 7214], [9725, 3782], [8983, 2721] ],
[ [8749, 3076], [4768, 3190], [6738, 4532], [9546, 3825], [6983, 4831] ]
               ];

var viewColors = [ "white", "blue", "gold", "green", "red", "maroon" ];
var interactionColors = [ "white", "lightblue", "yellow", "lightgreen",
   "lightcoral", "indianred" ];
var width = 1500, height = 1000;
var widthOfOnePack = 300, heightOfOnePack = 300;
var spaceBetweenPacks = 20;
var packCalculations = [];
  for (var count=0;
           count<3;
           count++   ){
    var pack = d3.layout.pack()
    .size([widthOfOnePack, heightOfOnePack])
    .value(function(d) { return d.size; });
    packCalculations[count] = pack.nodes(
    getJSONForOneWeekOfPopularityAndInteractionData(
                  count));
}
var svg = d3.select("body").append("svg")
   .attr("width", width)
   .attr("height", height);
var g1 = svg.selectAll("g")
         .data(packCalculations).enter().append("g")
         .attr("transform", function(d,i){
         return "translate(" + 
           (widthOfOnePack + spaceBetweenPacks)*i + "," + 
              height / 7 + ")" }
         );
g1.append("text")
.text(function(d, i){return "Week " + (i+1)})
  .attr("x", 60);
var g2 = g1.selectAll("g")
   .data(function(d){return d;})
   .enter()
   .append("g");

g2.append("circle")
  .attr("r", function(d, i){return d.r})
  .style("fill", function(d, i){return viewColors[i%6];})
  .attr("transform", function(d,i){
         return "translate(" + d.x + "," + d.y + ")"});

g2.append("circle")
  .attr("r", function(d, i){
        return ((d.interaction*d.r)/d.value);})
  .style("fill", function(d, i){return interactionColors[i%6];})
  .attr("transform", function(d,i){
         return "translate(" + d.x + "," + d.y + ")"});

g2.append("text")
  .attr("x", -15)
  .attr("y", 35)
  .attr("fill", "white")

  .text(function(d, i){return d.value})
  .attr("transform", function(d,i){
       return "translate(" + d.x + "," + d.y + ")"});

g2.append("text")
  .attr("x", -15)
  .attr("y", 5)

  //.attr("fill", "grey")
  .text(function(d, i){return d.interaction})
  .attr("transform", function(d,i){
       return "translate(" + d.x + "," + d.y + ")"});

function getJSONForOneWeekOfPopularityAndInteractionData(week){
var viewsAndInteractionJSON = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0; count<5; count++){
    viewsAndInteractionJSON += 
     "{ \"name\": \"" + viewColors[count+1] + 
      " \", \"size\": \"" + viewsAndInteraction[week][count][0] +       
      "\", \"interaction\": \"" + 
      viewsAndInteraction[week][count][1] + "\"     }   ";
  if (count<4) viewsAndInteractionJSON += ",";
  }
  viewsAndInteractionJSON += "]}";
  return JSON.parse(viewsAndInteractionJSON) ;
}

</script>
</body>

リスト 11 で行われている拡張 (太字で表示) は以下のとおりです。

  • 配列から JSON への変換ロジックは以前よりも複雑です。今回は、リスト 5 の JSON を生成し、その中にそれぞれが 2 つのデータ (人気度データとユーザーの交流データ) を持つリーフ・ノードを含めなければならないためです。
  • パック・レイアウト・オブジェクトの value() 関数は、引き続き d.popularity 属性を使用して機能し、user-interaction 属性を完全に無視します。パック・レイアウトのグラフィック計算は依然として人気度データに基づきます。ユーザーの交流データが関係してくるのは、外側の人気度を表す円の内部に、内側の円を描画するときのみです。ユーザーの交流を表す内側の円の r 属性値 (半径) を生成する部分 (リスト 11 に太字で示されています) に注目してください。ここが、ユーザーの交流データが使用される部分です。内側の円の半径を計算するために、ユーザーの交流データに外側の (人気度を表す) 円の半径と実際の人気度データの比を乗算します。この簡単なグラフィック操作により、内側の円は、D3 の内部で人気度を表す円の大きさを計算したときと同じ比を用いて計算した大きさになります。

棒グラフによる人気度データおよびユーザーの交流データの表現

これまでは、さまざまなレイアウトで円を使用してソーシャル・データを表現する方法を説明してきましたが、ここではちょっとしたバリエーションを加えます。図 5 に、同じソーシャル・データを円だけでなく棒グラフでも表現する例を示します。

図 5. 棒グラフによるソーシャル・データの表現
棒グラフによるソーシャル・データの表現

ご覧のように図 5 には、第 1 週から第 3 週の人気度データとユーザーの交流データが棒グラフでも表現されています。棒には、内側の円と外側の円の描画に使用しているのと同じ色と色調が使用されています。各棒の濃い色の部分は人気度を表し、薄い色調の部分はユーザーの交流を表します。

リスト 12 に、図 5 の棒グラフを描画する SVG コードを記載します (円を描画するコードは省略しています)。

リスト 12. 棒グラフの SVG
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="1500" height="1000">

<g>
<!--Tags for circles omitted.-->
</g>

<g>

<g transform="translate(100,250)">

  <g transform="translate(0,200)">
    <rect height="141.14" width="20" fill="blue"></rect>
    <rect height="41.04" width="20" fill="lightblue"></rect>
  </g>

  <g transform="translate(22,200)">
    <rect height="149.66" width="20" fill="gold"></rect>
    <rect height="41.78" width="20" fill="yellow"></rect>
  </g>

  <g transform="translate(44,200)">
    <rect height="74.98" width="20" fill="green"></rect>
    <rect height="31.72" width="20" fill="lightgreen"></rect>
  </g>

  <g transform="translate(66,200)">
    <rect height="76.92" width="20" fill="red"></rect>
    <rect height="28.52" width="20" fill="lightcoral"></rect>
  </g>

  <g transform="translate(88,200)">
    <rect height="91.96" width="20" fill="maroon"></rect>
    <rect height="52.64" width="20" fill="indianred"></rect>
  </g>
</g>

<g transform="translate(420,250)">

  <g transform="translate(0,200)">
    <rect height="119.44" width="20" fill="blue"></rect>
    <rect height="41.42" width="20" fill="lightblue"></rect>
  </g>

  <g transform="translate(22,200)">
    <rect height="113.44" width="20" fill="gold"></rect>
    <rect height="43.8" width="20" fill="yellow"></rect>
  </g>

  <g transform="translate(44,200)">
    <rect height="183.04" width="20" fill="green"></rect>
    <rect height="144.28" width="20" fill="lightgreen"></rect>
  </g>

  <g transform="translate(66,200)">
    <rect height="194.5" width="20" fill="red"></rect>
    <rect height="75.64" width="20" fill="lightcoral"></rect>
  </g>

  <g transform="translate(88,200)">
    <rect height="179.66" width="20" fill="maroon"></rect>
   <rect height="54.42" width="20" fill="indianred"></rect>
  </g>
</g>

<g transform="translate(740,250)">

  <g transform="translate(0,200)">
    <rect height="174.98" width="20" fill="blue"></rect>
    <rect height="61.52" width="20" fill="lightblue"></rect>
  </g>

  <g transform="translate(22,200)">
    <rect height="95.36" width="20" fill="gold"></rect>
    <rect height="63.8" width="20" fill="yellow"></rect>
  </g>

  <g transform="translate(44,200)">
    <rect height="134.76" width="20" fill="green"></rect>
    <rect height="90.64" width="20" fill="lightgreen"></rect>
  </g>

  <g transform="translate(66,200)">
    <rect height="190.92" width="20" fill="red"></rect>
    <rect height="76.5" width="20" fill="lightcoral"></rect>
  </g>

  <g transform="translate(88,200)">
    <rect height="139.66" width="20" fill="maroon"></rect>
    <rect height="96.62" width="20" fill="indianred"></rect>
  </g>
</g>

</g>
</svg>

リスト 12 には、お馴染みの <g> タグが並べられています。3 つの <g> タグのそれぞれにラップされているのは 1 週間分のデータです。この週ラッパー・タグごとに 5 つの子 <g> タグがあり、そこに実際の人気度データと交流のデータを表す <rect> タグのペアがラップされています。<circle> タグが円を描画するのと同じく、<rect> タグは長方形の棒を描画します。

<rect> タグには width 属性と height 属性があります。width 属性値はすべての <rect> タグで固定値である一方、height 属性値はその長方形の棒が表すソーシャル・データに比例します。幅が固定され、高さが可変となっている棒によって、棒グラフの印象がもたらされます。

この場合も同じく、<g> タグの transform 属性が、各棒をそれぞれの正しい位置に配置します。つまり、円の場合でも、棒の場合でも、SVG コードには根本的な違いがないということです。

図 5 の棒グラフを生成するための D3 ベースの JavaScript コードは、この記事のソース・コードのダウンロードに含まれています。

ナビゲーション・データの追加

1 つのページに各種のグラフィック・コンポーネントを表示する必要があるケースはよくあるため、最後にすべてのコンポーネントを 1 つの SVG キャンバス上に配置します。図 6 には、弟 1 回で記載した弦の図を使用して、第 1 週から第 3 週のナビゲーション・データが追加されています。

図 6. 単一の描画での円、弧、弦、レイアウト、および棒グラフ
単一の描画での円、弧、弦、レイアウト、および棒グラフ

図 6 の JavaScript もソース・コードのダウンロードに含まれています。


まとめ

この連載の限られた紙面のなかでの、データの視覚化の長い旅 ― 第 1 回の図 1 に示した単純に並べられた円の配置から、今回の記事で説明した高度な円の配置に至る旅 ― はこれで終わりです。この連載記事がきっかけとなって、皆さんが D3 Web サイトを探索すると見つけられる D3 の他の機能も調べてみたいという気になったことを願います。

D3 の大きな利点の 1 つは、そのベースが、トップクラスのビジネス・アプリケーションのフロントエンド・テクノロジーとして推奨される JavaScript であることです。JavaScript をサポートしているビジネス・アプリケーション・プラットフォーム (IBM Business Process Manager など) を使用しているとしたら、この連載記事で説明したグラフィック機能をそのままビジネス・アプリケーションに組み込むことができます。IBM Business Process Manager と、その外部の JSON ライブラリーおよび JavaScript ライブラリーに対するサポートについて学習するには、「参考文献」で IBM Business Process Manager インフォメーション・センターへのリンクを参照してください。


ダウンロード

内容ファイル名サイズ
サンプル・コードsource-code.zip10KB

参考文献

学ぶために

製品や技術を入手するために

  • D3: D3 JavaScript ライブラリーをダウンロードしてください。
  • IBM InfoSphere Streams: InfoSphere Streams をダウンロードして、何千ものリアルタイム・ソースからの情報を到着と同時に取り込み、素早く分析して相関させるアプリケーションを構築してください。
  • IBM InfoSphere BigInsights: InfoSphere BigInsights をダウンロードして、保管されている膨大な構造化および非構造化データを管理し、分析してください。

議論するために

  • developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、Wiki を調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Web development
ArticleID=944684
ArticleTitle=データの視覚化: 第 2 回 D3 のコンポーネント・レイアウトを使用する
publish-date=09122013