Amazon DynamoDB のデータを API Gateway と Angular( D3.js ) でサーバーレス可視化する
データを溜め込んでいく理由はビジネス要件によって多々ありますが、要件のひとつに「データを可視化したい」というものがあると思います。今回は DynamoDB 上のデータを Angular と D3 を使ってサーバーレスで可視化するサンプルを作ってみます。
やること
- Angular + D3.js でデータ可視化の準備をする
- DynamoDB にデータを用意する
- API Gateway で Angular 向けに変換して DynamoDB のデータを返す
- Angular で ローカルデータを使う代わりにHTTPリクエストする
Angular + D3.js でデータ可視化の準備をする
最初に、仮データを用意してローカルで可視化してしまいましょう。今回用意したデータはこちら。
| 候補者ID | 投票受付日 | 投票ポイント |
|---|---|---|
| MI12341011 | 2017-11-10T12:00:12+09:00 | 345.11 |
| MI12341011 | 2017-11-11T22:00:12+09:00 | 102.34 |
| MI12341011 | 2017-11-12T09:12:45+09:00 | 344.11 |
| SU40120055 | 2017-11-10T12:14:44+09:00 | 345.11 |
とある投票システムを仮想したデータです。特定の投票者IDに関するデータを、日別で見たいとしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 | export interface Vote { voteDate: string; value: number;}export const Votes: Vote[] = [ {voteDate: '2017-11-10T12:00:12+09:00', value: 345.11 }, {voteDate: '2017-11-11T22:00:12+09:00', value: 102.34 }, {voteDate: '2017-11-12T09:12:45+09:00', value: 345.11 }, {voteDate: '2017-11-13T09:12:45+09:00', value: 312.12 }, {voteDate: '2017-11-14T09:12:45+09:00', value: 267.34 }]; |
これを D3.js を使って棒グラフにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | import {Component, OnInit} from '@angular/core';import * as d3 from 'd3-selection';import * as d3Scale from 'd3-scale';import * as d3Array from 'd3-array';import * as d3Axis from 'd3-axis';import * as d3TimeFormat from 'd3-time-format';import {VoteDataService} from './vote-data.service';import {Votes} from './data';@Component({ selector: 'app-vote-bar-chart', templateUrl: './vote-bar-chart.component.html', styleUrls: ['./vote-bar-chart.component.css']})export class VoteBarChartComponent implements OnInit { subtitle = 'Bar Chart'; private margin = {top: 20, right: 20, bottom: 100, left: 50}; private width: number; private height: number; private x: any; private y: any; private svg: any; private dateFormat = d3TimeFormat.timeFormat('%Y-%m-%d'); constructor(private voteDataService: VoteDataService) { this.width = 900 - this.margin.left - this.margin.right; this.height = 500 - this.margin.top - this.margin.bottom; } ngOnInit() { this.initSvg(); this.initAxis(); this.drawAxis(); this.drawBar(); } private initSvg() { this.svg = d3.select('svg') .append('g') .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); } /** * x軸: rangeRoundで対象領域を指定(利用可能領域いっぱい)、要素間の余白を0.35に指定 * y軸: rangeで対象領域を指定 * x軸ドメイン: データオブジェクトのvoteDateを使う * y軸ドメイン: データオブジェクトのvalueを使う。リニアで指定しているので最小値と最大値さえ渡せば良い。 ****/ private initAxis() { this.x = d3Scale.scaleBand().rangeRound([0, this.width]).padding(.35); this.y = d3Scale.scaleLinear().range([this.height, 0]); this.x.domain(this.Votes.map((d) => new Date(d.voteDate))); this.y.domain([0, d3Array.max(this.Votes, (d) => d.value)]); } private drawAxis() { /** * X軸描画。 ****/ this.svg.append('g') .attr('class', 'axis axis--x').attr('transform', 'translate(0,' + this.height + ')') .call(d3Axis.axisBottom(this.x).tickFormat(this.dateFormat)) .selectAll('text') .style('text-anchor', 'end') .attr('dx', '-.8em').attr('dy', '-.55em').attr('transform', 'rotate(-90)'); /** * Y軸描画。 ****/ this.svg.append('g') .attr('class', 'axis axis--y') .call(d3Axis.axisLeft(this.y)); } /** * 棒グラフを描画。 ****/ private drawBar() { this.svg.selectAll('bar') .data(this.Votes) .enter().append('rect') .style('fill', 'DodgerBlue') .attr('class', 'bar') .attr('x', (d: any) => { return this.x(new Date(d.voteDate)); }) .attr('y', (d: any) => { return this.y(d.value); }) .attr('width', this.x.bandwidth()) .attr('height', (d: any) => { return this.height - this.y(d.value); }); }} |
実行すると以下のような結果が得られます。
さて、これを、DynamoDB のデータを使って表示できるよう、サーバーサイドを準備しましょう。
DynamoDB にデータを用意する
すでにデータが保存されている想定で、今回は手で追加してしまいます。 パーティションキーで candidateId を指定し、同一候補者のデータを Query で取得できるようにしておきます。
API Gateway で Angular 向けに変換して DynamoDB のデータを返す
DynamoDB のAPIを叩いて得られる結果データは、そのままアプリケーションで利用するには若干パースの手間がかかるため、間に API Gateway を間に置くことにしました。
エンドポイントの作成
Angular から見て、
- https://xxxxx/candidates/{candidateId}/votes
- 例:https://apigateway.com/candidates/MI12341011/votes
このようなURLで 「特定の立候補者の投票ポイント一覧」 を得られるようにしましょう。{candidateId} を指定することで DynamoDB 上のパーティションキーを指定できるようにします。リソースを下図のように作成してください。
リクエストの定義
リクエストの設定でやることは以下です。
- リクエストパスに含まれる
candidateIdを DynamoDB の Query 操作パラメータにする - DynamoDB への Query および そのリクエストボディを定義する
そして、「本文マッピングテンプレート」へ以下のように設定します。
1 2 3 4 5 6 7 | { "TableName": "vote", "KeyConditionExpression": "candidateId = :a", "ExpressionAttributeValues":{ ":a": { "S" : "$input.params('candidateId')"} }} |
レスポンスの定義
Query 操作の結果を、アプリケーション向けに少し加工します。こちらは、「統合レスポンス>マッピングテンプレート」で設定できます。
1 2 3 4 5 6 7 8 9 10 | #set($inputRoot = $input.path('$')){ "votes": [ #foreach($elem in $inputRoot.Items) { "voteDate": "$elem.voteDate.S", "value": "$elem.value.N" }#if($foreach.hasNext),#end #end ]} |
テストを実行して、以下のような結果が得られればOKです。
CORSの有効化
S3上のSPA(CoudFront経由で配信します)からのアクセスになりますので、CORSの設定が必要です。設定詳細については以下の記事を参考に設定してください。
API のデプロイ
ここまでできたら、API をデプロイします。実際にURLを叩いてみて、JSONが取得できるか試してみましょう。
Angular で ローカルデータを使う代わりにHTTPリクエストする
サーバーサイドの準備ができましたのでアプリケーション側に戻ります。現在、プログラム内にハードコードした配列を利用している状況ですので、Angular の Service 機能を使って HTTP 経由で先程のJSONを取得するよう修正します。
Service の作成と投票データ取得先の変更
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import {Injectable} from '@angular/core';import {Http} from '@angular/http';import {Vote} from './data';@Injectable()export class VoteDataService { private headers = new Headers({'Content-Type': 'application/json'}); constructor(private http: Http) { } getVotes(candidateId: string): Promise<Vote[]> { return this.http.get(this.voteUrl(candidateId), this.headers) .toPromise() .then(response => response.json().votes as Vote[]) .catch(this.handleError); } private voteUrl(candidateId: string): string { return `${this.voteBase}/${candidateId}/votes`; } private handleError(error: any): Promise<any> { console.error('An error occurred', error); return Promise.reject(error.message || error); }} |
Component 側は、Service 経由で取得した Vote を利用するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 | ngOnInit() { this.initData().then(() => { this.initSvg(); this.initAxis(); this.drawAxis(); this.drawBar(); });}private initData(): Promise<Vote[]> { return this.voteDataService.getVotes('MI12341011').then(votes => this.Votes = votes);} |
SPA のデプロイ
作成した Angular アプリケーションは、S3 にアップロードし、CloudFront を介してホスティングします。具体的な手順については以下を参考ください。
SPA を使ってみる
CloudFront で配信したアプリケーションにアクセスしてみます。裏側で API Gateway へアクセスし、DynamoDB のデータが取得できているようです。
ここで、DynamoDB にデータを追加してみます。
グラフを更新してみると、追加した分だけバーも増えていることがわかります。データの追加に対応することができました。
まとめ
DynamoDB と API Gateway を使って、DynamoDB のデータを取得するAPIを定義することができました。また、このレスポンス値を使って、S3に配備した AngularとD3.js製の SPA によって可視化することができました。
以前は、サーバーレスによるファイルアップロードシステムを作りました。今回はAWS上のデータを利用するパターンです。両者を組み合わせれば、データのアップロードから可視化までサーバーレスでできそうです。また実際に構築してみてまとめてみたいと思います。
ソースコード
バージョン情報
| 利用ツール・ライブラリ | バージョン |
|---|---|
| angular-core | 4.2.4 |
| d3 | 4.11.0 |