「Pure JavaScript vs Lodash」の結果をAngular app内でグラフ表示してみた

この記事は、[Business Bank Group Developers Advent Calendar](https://qiita.com/advent-calendar/2018/bbg_developers) 10日目の記事です。

---

こんにちは、BBGの清水です。
おとといのアドベントカレンダーで、[Pure JavaScript vs Lodash](https://qiita.com/kyokoshimizu/items/40fa64f29efcbcddfc49#comment-6ea459c889eba211a764)という内容を書きました。
せっかく計測したので、それをグラフで表示して可視化してみたいなー
Angular Materialさわってみたいなー
という思いを込めたアプリをAngularCLIで作ってみました。

### 開発環境
* Angular: 6.1.10
* Angular CLI: 6.1.5
* Angular Material: 7.1.1

### 実装
今回は、[MatTableModule](https://material.angular.io/)を使用してタブ表示をしています。
また、グラフは[NGX-CHARTS](https://swimlane.github.io/ngx-charts/#/ngx-charts/bar-vertical)を使用しました。
開発環境に@angular/material, @swimlane/ngx-chartsを入れたら、必要なmoduleをapp.moduleにimportするだけで簡単にタブ・グラフ表示ができました。

```typescript:app.module.ts
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatTableModule, MatTabsModule, } from '@angular/material';
```
使う側はこんな感じです。

```html:app.component.html
<div style="text-align:center">
  <h1>Pure JavaScript vs Lodash</h1>
</div>
<div class="body">
  <!-- Patternごとに結果をタブ切り替えで表示させた -->
  <mat-tab-group mat-align-tabs="start">
    <mat-tab label="Pattern1">
      <app-pattern1></app-pattern1>
    </mat-tab>
    <mat-tab label="Pattern2">
      <app-pattern2></app-pattern2>
    </mat-tab>
    <mat-tab label="Pattern3">
      <app-pattern3></app-pattern3>
    </mat-tab>
  </mat-tab-group>
</div>
```

```html:pattern1.component.html
<div class="wrapper">
  <div class="content">
    <button mat-stroked-button (click)="onClick()">
      StartMeasuring!
    </button>
    <ng-container *ngFor="let item of displayData">
      <div class="average">
        <span class="{{item.class}}">{{item.label}}</span>
        <span class="value">{{item.average}} (ms)</span>
      </div>
    </ng-container>
  </div>
  <!-- グラフ表示 -->
  <ngx-charts-line-chart
    [view]="view"
    [results]="inputData"
    [xAxis]="true"
    [yAxis]="true"
    [legend]="true"
    [showXAxisLabel]="true"
    [showYAxisLabel]="true"
    xAxisLabel="times"
    yAxisLabel="(ms)">
  </ngx-charts-line-chart>
</div>
```

```typescript:pattern1.component.ts
import { Component } from '@angular/core';
import * as _ from 'lodash';

interface SeriesInterface {
  value: number;
  name: number;
}
interface InitialDataInterface {
  a: number;
}

@Component({
  selector: 'app-pattern1',
  templateUrl: './pattern1.component.html',
  styleUrls: ['./pattern1.component.css']
})

export class Pattern1Component {
  view = [750, 450];
  roopCount = 50;
  inputData: { name: 'PureJS' | 'Lodash', series: SeriesInterface[] }[] = [];
  displayData: { label: string, average: number, class: string }[] = [];

  constructor() {}

  onClick() {
    const data1: InitialDataInterface[] = [];
    const data2: InitialDataInterface[] = [];
    Array.from({ length: 10000 }, () => {
      const num = Math.random();
      data1.push({ a: num });
      data2.push({ a: num });
    });

    const result1: SeriesInterface[] = [];
    const result2: SeriesInterface[] = [];
    Array.from({ length: this.roopCount }, ({}, index) => {
      result1.push({ value: this.pureJS(data1), name: index });
      result2.push({ value: this.lodash(data2), name: index });
    });

    this.inputData = [ { name: 'PureJS', series: result1 }, { name: 'Lodash', series: result2 } ];
    this.inputData.forEach((item, i) => {
      this.displayData.push({
        label: `${item.name}Average: `,
        average: this.calcAverage(item.series),
        class: `label${i + 1}`,
      });
    });
  }

  calcAverage(result: SeriesInterface[]): number {
    return _.sum(result.map(o => o.value)) / this.roopCount;
  }

  pureJS(data: InitialDataInterface[]): number {
    const startMs = performance.now();
    let sum = 0;
    data.map(o => o.a)
      .filter(a => a % 2)
      .forEach(n => sum += n);
    return performance.now() - startMs;
  }

  lodash(data: InitialDataInterface[]): number {
    const startMs = performance.now();
    _.chain(data).map('a')
      .filter(a => a % 2).sum().value();
    return performance.now() - startMs;
  }
}
```
Angular Material はcomponentごとに[stackblitz](https://run.stackblitz.com/api/angular/v1?file=app%2Ftab-group-basic-example.ts)が用意されていて、ちょっとした動作確認やイメージを掴むのにとても便利でした。
MatTableModuleのタブ切り替えのUXがかっこよくて個人的にとても好きです。

グラフは、NGX-CHARTSのLine Chartを使用しています。
グラフのマウスオーバー時の挙動はこんな感じです。
<img width="677" alt="スクリーンショット 2018-12-09 23.50.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/238961/598b6aa0-378a-e8ea-a06e-2037972e5eba.png" width="50%">



### 可視化の結果
（グラフをみやすくするために、平均値はそれぞれ50, 50, 10回の結果を表示しています。）
#### Pattern 1
<img width="1416" alt="スクリーンショット 2018-12-09 23.35.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/238961/3aada95a-4d9d-b07b-6eb2-c1b5dc382828.png">
平均値はPureJSが早いですが全体的に結果がまばらです。
あと、Lodashの最初の１回目に時間かかっているのが気になります。。（データの作り方が悪いのかな。。）
#### Pattern 2
<img width="1369" alt="スクリーンショット 2018-12-09 23.35.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/238961/16d3830a-7550-6970-a85d-df12d96cdf87.png">
PureJSがLodashを上回ることは一度もなかったです。
#### Pattern 3
<img width="1372" alt="スクリーンショット 2018-12-09 23.35.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/238961/039bfa12-6a60-dc8d-a988-e08945c843a3.png">
前回の記事のコメントで[@pocke](https://qiita.com/pocke)さんが、PureJSのより良い実装を教えてくれたのでそちらのケースをPureJSAverage2として追加しています（ありがとうございます！）
その結果、処理の仕方によって計測結果が大きく変わることが分りました。（PureJSAverage1, PureJSAverage2）


### さいごに
今回は、Angular CLIとNGX-CHARTSを使って簡単にデータを可視化することができました！
やはりグラフ化されるとデータが見やすくていいですね。以上です！

明日は、[@keitatakahashi](https://qiita.com/keitatakahashi)さんです。
お願いします！