独自のビジュアライゼーションの作成
すべてのビジュアライゼーションは同じメソッドシグネチャに従っており、使い方はどれも同じです。そのため、新しいビジュアライゼーションを簡単に作成できます。次に示すシグネチャに従うJavaScriptオブジェクトを作成し、他の開発者にも使えるようにするだけです。
// namespace, to avoid conflicts with other visualizations and javascript objects
// living in the same page as this visualization.
var my_domain = {};
// constructor for the MyVisualization object. The 'container'
// parameter is the placeholder DOM element into which
// you will draw your visualization.
my_domain.MyVisualization = function(container) {
// store the container into an instance variable for later use.
this.container = container;
};
// The draw() method receives a DataTable and an options object.
my_domain.MyVisualization.prototype.draw =
function(data, options) {
// ... implement your visualization code here.
}
こうしたスタブが定義できたら、使用するビジュアライゼーションに必要なコードをdraw()メソッドに実装します。
本稿執筆時のビジュアライゼーションギャラリーには、まだツリーマップ表示用のビジュアライゼーションが見当たりません。ツリーマップは、ツリー構造のデータを入れ子状の矩形で表示する手法で、ツリー内の各ノードにスカラー値を与える場合に特に重宝します。これは新しいGoogleビジュアライゼーションとして実装するのに適した対象と言えます。
話を簡単にするため、ここでは描画用コードを一から実装するのではなく、David Cowie氏が作成したツリーマップ描画用のJavaScriptライブラリ「Js-Treemap」を再利用して、これをビジュアライゼーションに組み込みます。図2は、組織図(図1を参照)で使ったのと同じデータをツリーマップで表示したものです。ただし、ツリー内のノードごとに数値の設定が必要になります。
Js-Treemapライブラリを使えば、ツリーマップの描画は比較的簡単です。次の手順を実行するだけで済みます。
- 主体となる図表とその操作用コントロールを含むHTML要素を作成します。
- 階層構造を表現するために、ビジュアライゼーションの
DataTableをネストされた一連のTreeNodeオブジェクトに変換します。TreeNodeクラスは、Js-Treemapライブラリで定義されています。 - ツリーマップ自体の描画処理を行う
DivTreeMapインスタンスを、指定のコンテナDOM要素内に作成します。DivTreeMapの定義もJs-Treemap内にあります。 - 必要に応じて、ツリーマップのクリックに対するイベントリスナを操作用コントロールにバインドします(ツリーマップの各領域の拡大/縮小など)。
リスト2を見れば、こうした手順のすべてが、先ほどのスタブを利用してどのようにGoogleビジュアライゼーション内に実装されるかが分かります。
// namespace
var ns = {};
ns.TreeMap = function(container) {
this.container = container;
};
ns.TreeMap.prototype.draw = function(data, options) {
var self = this;
// parse options and fallback to defaults for unspecified ones.
options = options || {};
options.width = options.width || '50%';
options.height = options.height || 300;
// create the required DOM elements
var treeMapContainer = document.createElement('DIV');
treeMapContainer.style.width = options.width;
treeMapContainer.style.height = options.height - 20;
treeMapContainer.style.backgroundColor = '#77A';
this.container.appendChild(treeMapContainer);
var statusBar = document.createElement('DIV');
statusBar.style.height = 20;
var backButton = document.createElement('BUTTON');
backButton.innerHTML = 'Back';
backButton.style.right = 0;
statusBar.appendChild(backButton);
var infoBox = document.createElement('SPAN');
statusBar.appendChild(infoBox);
this.container.appendChild(statusBar);
// Parse the Gviz DataTable and render it.
var rootNode = this.createTree(data);
var map = new DivTreeMap(treeMapContainer,
rootNode,
{ shader: new TreeNodeShader(rootNode) });
// Attach events.
map.onZoomClick = function() {
map.zoom(); backButton.disabled = false;
};
map.onMouseOver = function(node) {
infoBox.innerHTML = node.toString();
};
backButton.onclick = function() {
backButton.disabled = ( map.unzoom() === 0 );
};
};
生成されるビジュアライゼーションの高さと幅を設定するオプションの構文解析を行うコードや、ユーザーがオプションを指定しなかった場合にデフォルト値を使用する部分に目を向けてください。コンストラクタに渡されたcontainerオブジェクトの内部で図がどのように生成されるかにも注目です。draw()関数が受け取るDataTableオブジェクトの解析は、createTree()メソッド内で行われます。リスト3にこのビジュアライゼーションの実装を示します。
// Parses the GViz DataTable and returns a Tree suitable to be used with
// the treemap javascript library.
ns.TreeMap.prototype.createTree = function(data) {
// Get a list of all non-leaf nodes
var parentsMap = {};
var parents = data.getDistinctValues(1);
for (var i=0; i<parents.length; i++) {
parentsMap[parents[i]] = {name: parents[i], childs: []};
}
// Populate each parent node with the list of direct childs
for (var i=0; i < data.getNumberOfRows(); i++) {
var name = data.getValue(i, 0);
var parent = data.getValue(i, 1);
var quantity = data.getValue(i, 2);
parentsMap[parent].childs.push({name: name, quantity: quantity});
}
// Assemble the tree
for (var parent in parentsMap) {
var childs = parentsMap[parent].childs;
for (var i = 0; i<childs.length; i++) {
if (parentsMap[childs[i].name]) {
childs[i] = parentsMap[childs[i].name];
}
}
}
// Transform the tree in a set of TreeNode, TreeParentNode objects
// (the expected input structure for the treemap js library)
var rootNode = this.populateTree(parentsMap["Root"]);
return rootNode;
};
// Recursively populates a TreeNode structure.
ns.TreeMap.prototype.populateTree = function(node) {
if (!node.childs) {
// leaf node
return new TreeNode( node.name, node.quantity );
} else {
// tree node
var treenodes = [];
for (var i = 0; i < node.childs.length; i++) {
treenodes.push(this.populateTree(node.childs[i]));
}
return new TreeParentNode(node.name, treenodes);
}
};
このコードでは、DataTableに用意された各種関数を使ってそのデータを処理しています。データセット内の全セルに対する反復処理にはGetNumberOfRows()とgetNumberOfColumns()が、各セルの値の読み取りにはgetValue()が使えます。getDistinctValues()といった他の関数は、行および列にわたるデータの集約処理を行います。
ツリーマップビジュアライゼーションは、DataTableに3つの列が含まれることを前提としています。最初の2つはノードの名前とその親ノードの名前で、組織図の場合と同じです。残る1つは数値型の列で、リーフノードに与えられるスカラー値を表します。
この場合も、先ほどの組織図の例と同様、drawVisualization()関数の更新によって、DataTableの列を増やしたり、新たに作成したツリーマップビジュアライゼーションをWebページに追加したりできます。リスト4に最終的なコードを示します。
function drawVisualization() {
// Create and populate a data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Name');
data.addColumn('string', 'Parent');
data.addColumn('number', 'Quantity');
data.addRows(10);
// Add more data rows and cells here
var row = 0;
addRow(data, row++, "Apples", "Root");
addRow(data, row++, "Braeburn", "Apples", 1);
addRow(data, row++, "Cox", "Apples", 2.2);
addRow(data, row++, "Golden Delicious", "Apples");
addRow(data, row++, "English", "Golden Delicious", 1);
addRow(data, row++, "French", "Golden Delicious", 2);
addRow(data, row++, "Bananas", "Root");
addRow(data, row++, "Caribbean", "Bananas", 2.5);
addRow(data, row++, "Central American", "Bananas", 1.5);
addRow(data, row++, "Clementines", "Root", 3);
var orgchart = new google.visualization.OrgChart(
document.getElementById('orgchartdiv'));
orgchart.draw(data, {
selectionColor: 'yellow'
});
// Instantiate our Treemap visualization.
var treemap = new ns.TreeMap(document.getElementById('treemapdiv'));
// Draw our treemap with the data we created locally.
treemap.draw(data, {
width: '50%',
height: '300'
});
}
ここで作成したビジュアライゼーションにより、サンプルのWebページは図3のようになります。


