目次


D3 および Angular を使用したカスタム・データ視覚化

JavaScript アプリで再利用可能な視覚化を作成する

Comments

データは至るところに溢れていますが、それらのデータで何ができるかを知ることが重要です。データ視覚化とは、関連するデータを簡潔で、傾向を導き出せるような形でユーザーに提示する手法です。

Web に出回っている多数のライブラリーを利用すれば、線グラフや棒グラフ、そして円グラフなどといった視覚化を作成できますが、そのような一般的なタイプの視覚化とは異なる斬新な方法でデータを提示しなければならないこともあります。その場合、目的とする新しい視覚化を自分で作成しなければなりません。カスタム視覚化を作成する際に利用できる JavaScript ライブラリーとしては、D3.js があります。D3.js は、組み込み視覚化を提供するのではなく、独自の視覚化を作成するためのビルディング・ブロックを揃えた、柔軟なデータ駆動型のドキュメント操作ライブラリーです。多くの場合、D3.js を利用して作成する視覚化は SVG (Scalable Vector Graphics) 形式になりますが、使い慣れた従来の HTML をデータにバインドすることもできます。

このチュートリアルでは、D3.js を AngularJS アプリケーションに統合する方法を説明します (チュートリアル用のサンプル・コードでは JavaScript バージョンとして ECMAScript 2015 を使用していますが、コードを機能させるために、ECMAScript 2015 を使用しなければならないわけではありません)。

時には、一般的なタイプの視覚化とは異なる斬新な方法でデータを提示しなければならないこともあります。その場合、目的とする新しい視覚化を自分で作成しなければなりません。

D3 を使用してデータ・リストを作成する

以下の D3 コードは、順序なしリストを作成し、そのリストに 3 つの項目 (angulard3c3) を追加します。

d3.select('body')
  .append('ul')
  .selectAll('li')
  .data(['angular', 'd3', 'c3'])
  .enter()
  .append('li')
  .text(d => d);

上記のコードにより、以下の視覚化がレンダリングされます。

レンダリングされたデータ・リストのスクリーンショット

データの重みに基づく視覚化を作成する

以下の D3 コードで作成する SVG 視覚化には、3 つの円が表示されます。これらの円の半径とオフセットには、それぞれに指定された値が反映されます。

d3.select('body')
    .append('svg')
    .selectAll('circle')
    .data([1000, 10000, 250000, 15000])
    .enter()
    .append('circle')
    .attr('r', d => Math.log(d))
    .attr('fill', '#5fc')
    .attr('stroke', '#333')
    .attr('transform', (d, i) => {
        var offset = i * 20 + 2 * Math.log(d);
        return `translate(${offset}, ${offset})`;
    });

このコードにより、以下のように視覚化されます。

バブルの視覚化のスクリーンショット

視覚化を Angular ディレクティブでラップする

Angular で DOM を操作する際は、常にディレクティブの中で操作するのが最善の策です。複数の要素をグループにまとめて将来も再利用できるようにするには、カスタム・データ視覚化を Angular モジュールでラップすることが賢い方法となります。

以下のコードが作成する新しいディレクティブは、<bubbles></bubbles> をマークアップに追加することによってインスタンス化できます。

angular.module('pi.visualizations').directive('bubbles', bubbles);
function bubbles() {
    return {
        restrict: 'E',
        controller: () => {},
        bindToController: true,
        controllerAs: 'viz'
    };
}

ここでは、「データの重みに基づく視覚化を作成する」のセクションで使用したサンプルを使用できるよう、ディレクティブの名前として bubbles を使用しています。現時点では、このディレクティブによって達成される成果は何もないので、何らかの実装の詳細を追加する必要があります。

このディレクティブが受け入れることができるのは、視覚化で使用するデータ・ポイントの配列です。したがって、ディレクティブのプロパティーとして配列を追加するために scope プロパティーを使用し、このプロパティーで values プロパティーをコントローラーにバインドします。また、この視覚化は SVG として処理されることになるので、このディレクティブは視覚化のテンプレートとしての役割を果たすこともできます。そのように設定するために使用しているのが、template プロパティーです。

angular.module('pi.visualizations').directive('bubbles', bubbles);
function bubbles() {
    return {
        scope: {
            values: '='
        },
        template: '<svg width="900" height="300"></svg>',
        restrict: 'E',
        controller: () => {},
        bindToController: true,
        controllerAs: 'viz'
    };
}

D3 を使用してチャートを生成するための処理は、ディレクティブの link 関数で行われます。

視覚化を独立させる

すべての要素を link 関数にインライン化することもできますが、このコードには Angular に対する特定の依存性はないので、視覚化のロジックをスタンドアロン・コンポーネントとして作成するほうが有益です。疎結合の考えを推進するこの手法により、Angular を使用しないプロジェクトも含め、他のプロジェクトでもこの視覚化を利用できるようになります。

この例では、視覚化をレンダリングするクラスを組み込むために CommonJS を使用します。

// There is a dependency on d3 for the visualization
var d3 = require('d3');

// A class that renders values on a logarithmic scale
function Bubbles(target) {
    this.target = target;
}

// Does the work of drawing the visualization in the target area
Bubbles.prototype.render = function (values) {
    d3.select(this.target)
        // Get the old circles
        .selectAll('circle')
        .data(values)
        .enter()
        // For each new data point, append a circle to the target SVG
        .append('circle')
        // Apply several style attributes to the circle
        .attr('r', d => Math.log(d))
        .attr('fill', '#5fc')
        .attr('stroke', '#333')
        .attr('transform', (d, i) => {
            // This moves the circle based on its value
            var offset = i * 20 + 2 * Math.log(d);
            return `translate(${offset},${offset})`;
    });
};

// Does any cleanup for the visualization (e.g., removing event listeners)
Bubbles.prototype.destroy = function () {}

// Exports the visualization
module module.exports = Bubbles;

このクラスを使用する方法は以下のとおりです。

var Bubbles = require('./Bubbles');
// The target SVG element
var svg = document.getElementById('my-visualization');
var visualization = new Bubbles(svg);
visualization.render([1000, 25000, 3000000, 120000, 25, 10203]);

クラスをディレクティブに結び付ける

SVG を作成するディレクティブと、データをレンダリングするクラスが用意できました。次は、この 2 つを 1 つに結合します。前述のとおり、レンダリングの処理は link 関数で行われます。

angular.module('pi.visualizations').directive('bubbles', bubbles);
function bubbles() {
    return {
        scope: {
            values: '='
        },
        template: '<svg width="900" height="300"></svg>',
        restrict: 'E',
        controller: () => {},
        bindToController: true,
        controllerAs: 'viz',
        link: function (scope, element, attrs, ctrl) {
            // Bring in the Bubbles class
            var Bubbles = require('./Bubbles');
            // Create a Bubbles visualization, targeting the SVG element from the template
            var visualization = new Bubbles(element.find('svg')[0]);
            // Watch for any changes to the values array, and when it changes, re-render the chart
            scope.$watchCollection(() => ctrl.values, () => {
                visualization.render(ctrl.values ? ctrl.values : []);
            });
            scope.$on('$destroy', () => {
                // If we have anything to clean up when the scope gets destroyed
                visualization.destroy();
            });
        }
    };
}

アプリケーション内で視覚化をレンダリングするコードは以下のとおりです。

<bubbles values="[1000, 25000, 3000000, 120000, 25, 10203]"></bubbles>
最終的なバブルの視覚化のスクリーンショット
最終的なバブルの視覚化のスクリーンショット

将来に向けた計画: Angular 2

Angular 2 は、コンポーネントの新しい開発方法を確立します。Angular 2 の側面には、馴染み深いものもあれば、大幅に異なるものもありますが、突き詰めるところ、Angular 2 アプリケーションでも、これまで説明したのと同じプロセスに従って再利用可能なデータ視覚化を作成することができます。このセクションに記載するコードは、TypeScript という、Angular 2 でよく使われている言語で作成されています。

前と同じく、Angular 固有のコードと視覚化のコードを別々のモジュールに分けるために、視覚化コードを bubbles.chart.ts に含めています。機能的には、TypeScript と通常の JavaScript コードの間に大きな違いはありません。

// There is a dependency on d3 for the visualization; can be included as
// <script src="//d3js.org/d3.v2.js"></script>
declare var d3;

// Exports the visualization module
export class BubblesChart {
    target: HTMLElement;
    constructor(target: HTMLElement) {
        this.target = target;
    }

    render(values: number[]) {
        d3.select(this.target)
        // Get the old circles
        .selectAll('circle')
        .data(values)
        .enter()
        // For each new data point, append a circle to the target SVG
        .append('circle')
        // Apply several style attributes to the circle
        .attr('r', d => Math.log(d))
        .attr('fill', '#5fc')
        .attr('stroke', '#333')
        .attr('transform', (d, i) => {
                // This moves the circle based on its value
                var offset = i * 20 + 2 * Math.log(d);
                return `translate(${offset}, ${offset})`;
        });
    }

    destroy() {
    }
}

Angular からの最大の変更点と言えば、Angular 2 ではディレクティブに複数の分類があることです。この例のディレクティブはテンプレートを使用するため、ここで必要となるのはコンポーネント・ディレクティブです。このコンポーネントは、bubbles.component.ts に含まれています。

// Loads some required modules from Angular.
import {Component, Input, OnChanges, AfterViewInit, ViewChild} from '@angular/core';
// Loads the code needed to manipulate the visualization
import {BubblesChart} from './bubbles.chart';

// Identifies the class as a component directive that will be associated
// with `bubbles` elements in the DOM, and will include the specified markup as its template
@Component({
    selector: 'bubbles',
    template: '<svg #target width="900" height="300"></svg>'
})
export class Bubbles implements OnChanges, AfterViewInit {
  // Declares values as a data-bound property
    @Input() values: number[];
  // Gets a reference to the child DOM node
    @ViewChild('target') target;
  // An instance of the BubblesChart
    chart: BubblesChart;

    constructor() {
    }

  // Lifecycle hook that is invoked when data-bound properties change
    ngOnChanges(changes) {
        if (this.chart) {
            this.chart.render(changes.values);
        }
    }

  // Lifecycle hook for when the component's view has been fully initialized
    ngAfterViewInit() {
    // We have to wait until the view has been initialized before we can get the
    //DOM element to bind the chart to it
        this.chart = new BubblesChart(this.target.nativeElement);
        this.chart.render(this.values);
    }

}

Angular 2 では、あるコンポーネントを別のコンポーネントに含める場合、対象のコンポーネントを、それを包含するコンポーネントのメタデータの directives 配列プロパティーに追加する必要があります。以下のコードによって、バブル・チャートを my-app コンポーネント内でレンダリングすることが可能になります。

import {Component} from '@angular/core';
import {Bubbles} from './bubbles/bubbles.component';

@Component({
    selector: 'my-app',
    template: '<bubbles [values]="[1000, 25000, 3000000, 120000, 25, 10203]"></bubbles>',
  directives: [Bubbles]
})
export class MyApp {
  constructor() {
  }
}

まとめ

このチュートリアルでは、D3.js と Angular または Angular 2 を使用して、再利用可能なカスタム・データ視覚化を作成する単純な例を紹介しました。この例を基に、さらに多くのプロパティーをディレクティブに追加して視覚化をより柔軟に構成してから、提供されたデータ・セットを正確に表すように render 関数を更新してください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source, ビジネス・アナリティクス
ArticleID=1036550
ArticleTitle=D3 および Angular を使用したカスタム・データ視覚化
publish-date=08252016