daikiojm’s diary

ブログを書くときがやってきたどん!

bitbankのCandlestick APIを使ってAngularでチャートを描画する

多くの仮想通貨取引所では、APIトレーダー向けに取引のための注文APIや価格情報を取得するためのAPIが公開されている。
今回は、bitbankのpublic APIのうちcandlestickを使ってAngularと ngx-chartsを使ってチャートを描画してみたので、ポイントとサンプルコードを記録しておく。

f:id:daikiojm:20180929225952p:plain

Candlestickについて

f:id:daikiojm:20180929225821p:plain

今回使用するcandlestick APIは、ろうそく足チャートを描画するのに必要な情報を取得できるAPIで、以下の情報が含まれる。

  • 期間開始時の価格
  • 期間内の最高値
  • 期間内の最安値
  • 期間終了時の価格
  • 期間内の出来高合計

OHLCVデータ

CandlestickデータはOHLCVというフォーマット(フォーマットというより並び順)で配信されていて、それぞれは以下の情報とマッピングすることができる。
実際にはOHLCV + 期間を示すタイムスタンプが配信されている。
ちなみに、OHLVまでを 四本値 と呼んだりもするらしい。

  • open(O) :期間開始時の価格
  • high(H) :期間内の最高値
  • low(L) :期間内の最安値
  • close(C) :期間終了時の価格
  • volume(V) :期間内の出来高合計
  • ※timestamp :UnixTime

参考: https://docs.bitbank.cc/#!/Candlestick/candlestick

OHLCVとngx-chartsのseriesとのマッピング

今回作ったサンプルでは、ロウソクやヒゲは描画せず、単純なlineチャートとして表現したいので、OHLCVから必要な価格情報とtimestampのみを使用して、ngx-chartsのseriesの形にマッピングする。
といっても、以下のようなTypeScriptのインターフェースを定義して、RxJSのmapオペレータの中で単純なマップをするだけ。

// ngx-chartsに渡すデータのtype (nameがx,valueがy軸に対応する)
export interface Series {
  name: number;
  value: number;
}

次のようなマッピング用のメソッドを用意して、cnaclestickの6番目の要素とのtimestampと1番目の要素の開始時の価格でlineチャートを描画する情報を作っていく。

private mapSeries(candleStick: CandleStick): Series[] {
  return candleStick.ohlcv.map((data) => ({
    name: data[5],
    value: +data[0],
  }));
}

実際にはRxJsのmapオペレータの中で上記のマッピング用のメソッドを呼び出す。

  private fetchCandlestick(
    pair: string,
    unit: string,
    period: string
  ): Observable<Series[]> {
    const url = this.getCnadlestickUrl(pair, unit, period);
    return this.http.get<BbApiResponse<BbCandleStickResponse>>(url).pipe(
      map((data) => data.data.candlestick[0]),
      map((candlestick) => this.mapSeries(candlestick))
    );
  }

完成したもの

リポジトリはこれ
daikiojm/angular-bitbank-public-chart -GitHub

StackBlitzで動いてるやつ
daikiojm/angular-bitbank-public-chart -StackBlitz

通過ペアと期間を指定して、現在から1日前、1週間前、1ヶ月前、1年前それぞれの価格lineチャートが取得できるようになっている。
かなりざっくりな実装だが、価格推移の雰囲気を表現するのには十分じゃないかと思う。
各取引所でこういったpublicデータを配信するAPIがが公開されているので、リアルタイムデータと合わせてまとめサイトみたいなのを作ってみても面白そう。

参考

ErgoDox EZのファームウェアをMacでビルドしようとしたら辛かったのでDcokerでビルドした

ErgoDox EZは、GUIからキーマップを作成して、.hex ファイルのファームウェアをダウンロードできるサイトを用意してくれている。

ErgoDox EZ Configurator

ただ、ここから変更できる内容には制限があり、少し凝ったキーマップに変更しようとすると、キーマップファイルを自分で修正してソースからビルドする必要がある。 キーマップの作成時には、以下の記事が大変参考になった。

qiita.com

自分の場合、MacCommand キーを複数のキーに割り当てたくなりGUIからのキーマップ作成を諦め、ソースからビルドすることを決心した。

Macでのローカルビルドを試みた

MacでErgoDox EZのファームウェアをローカルビルドするには avr-gcc というavrマイコン用のビルドツールをインストールすればいいことがわかった。
brewで一発インストールできるとの記事を見て、軽い気持ちで試みるもmake実行中にコンパイルエラーが発生し、brewやビルドツール周りの環境を確認するも未だに解決していない状態。
一刻も早くキーマップを変更して快適なErgoDoxライフを送りたかったので、homebrew-avrリポジトリにIssueを投げつつ、Dockerで用意されたビルド環境を使うことにした。

今回の状況は、以下のIssueの通り

github.com

Dockerでビルド

ErgoDox EZのファームウェアが配布されているリポジトリ qmk/qmk_firmware にビルド用のDockerfileがあったので、これを使い使ってみる。 ひとまず、リポジトリに含まれているデフォルトのキーマップをビルドする。

クローンしておく

$ git clone git@github.com:qmk/qmk_firmware.git
$ cd qmk_firmware

Dockerfileからイメージをビルド
あとで気づいたが、 edasque/qmk_firmware を使ってもいいかもしれない。

$ docker build -t qmk_firmware . 

対象のキーボードの種類と作成した(今回はデフォルト)キーマップを環境変数に指定して、dockerを起動する

$ docker run -e keymap=default -e keyboard=ergodox_ez --rm -v (pwd):/qmk:rw qmk_firmware

正常にビルドできれば、 ergodox_ez_default.hex というファイルが出力されているはず。

終わりに

ひとまず、当初考えていたキーマップに変更することができて概ね満足です。
まだまだ、ErgoDox自体に慣れるにまでに時間がかかりそうで、暇な時にタイピングゲームとかしている状況。
(学生の頃にイキってUS配列のMacを使い始めたものの、なかなか慣れなかったときの感覚)
このブログもErgoDoxで書いてるけど、正直苦行なので早く慣れていきたい。

ScottyでAngularSPAをS3+CloudFrontにデプロイ

CI環境上または、開発環境上でビルドしたファイルをS3にデプロイする際には、s3 syncなどのコマンドを使うことが多いかと思います。
そんな時、Scottyが割と便利だったのでAngular CLIで作成したSPAをデプロイする前提で簡単に使い方を紹介します。

前提

  • AWS credentialsが設定されている
  • デプロイ先のS3バケットは作成済みである
  • Node.js(v6.9以上)が使える
  • Angular CLIがインストールされている

ひとまずやってみる

今回は、ng newした直後のAnguarプロジェクトをng buildした結果生成されるファイルをScottyを使ってデプロイします。

Scottyをグローバルインストール

$ npm install -g scottyjs

ビルド

$ ng build --prod

scottyコマンドを叩きデプロイ

$ scotty --bucket scotty-angular-test --region ap-northeast-1 --spa --update --source ./dist

CloudFrontを作成しない場合

$ scotty --bucket scotty-angular-test --region ap-northeast-1 --spa --update --nocdn --source ./dist

※ 実行時のオプションパラメータはREADMEを参照

f:id:daikiojm:20180313093858p:plain

scottyコマンドを実行した後数分で、CloudFront ウェブディストリビューションの状態が有効になり、CloudFrontに設定されたURLでS3にでデプロイされたAngular SPAにアクセスできるようになります。

npm scriptsにビルド+デプロイコマンドを定義

上記の手順をnpm scriptsにまとめてしまいます。

{
  "name": "scotty-test",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "deploy": "npm run build && scotty --bucket scotty-angular-test --region ap-northeast-1 --spa --update --source ./dist",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
...

また、Scottyがグローバルインストールされていない環境でも利用できるよう、devDependencyに登録しておきます。(この場合グローバルにインストールされている必要はない)

$ npm install --save-dev scottyjs

所感

現状S3への簡単なデプロイ方法として s3 syncコマンドを使う方法があるけど、Scottyの場合、devDependencyでプロジェクトに追加して、npm scriptsから呼び出せるので別でビルド(orローカル開発)環境にAWS CLIが必要ないのがよい。
「手元でサクッとビルドしてとりあえず確認環境を構築したい!」といった用途に向いているかなぁという印象。

参考

Googleドキュメントを使って音声ファイルを文字起こしする小技

Google ドキュメントの音声入力機能を使い、mp3ファイルやブラウザ上でストリーミング再生した音声ファイルを文字起こしする方法(小技)を紹介します。
ドキュメント エディタ ヘルプ -Google

標準機能では、入力元の音声はマイク入力のみの対応となっていて、直接音声ファイルを入力することはできませんが、Soundflowerというフリーソフトを使い、音声ファイルをマイク入力として扱うことで音声を直接文字起こしすることができます。

必要なもの

  • Mac (SoundflowerはMac用App)
  • Soundflower
  • 入力音声ファイル or Webストリーミング音声

手順

手順はいたって簡単です。

Soundflowerのインストール

今回はGitHubからdmgファイルをダウンロードしてインストールしました。 https://github.com/mattingalls/Soundflower/releases/tag/2.0b2

f:id:daikiojm:20180207223711p:plain

サウンド設定

Soundflowerのインストールが済んだら、システム環境設定 -> サウンド を開き、「出力」と「入力」をそれぞれ Soundflower (2ch) に変更します。

出力 f:id:daikiojm:20180207223616p:plain

入力 f:id:daikiojm:20180207223638p:plain

Googleドキュメントで使用する入力デバイスを選択する

Googleドキュメントを開き、ブラウザのアドレスバーの右端に表示されるビデオマークをクリックして、入力デバイスを選択します。
(Google Chromeの場合) f:id:daikiojm:20180207224400p:plain

音声ファイルを再生する

文字起こしをしたい音声ファイルを再生します。
※ この時、出力はSoundflowerになっているため、内蔵スピーカやイヤフォンから音声は聞こえません。

f:id:daikiojm:20180207224608p:plain

Googleドキュメントで音声入力

Googleドキュメントを開き、通常の音声入力を行う手順で、マイクをOnにします。
※ ウィンドウが非アクティブになると音声入力が停止されるので注意が必要です。

f:id:daikiojm:20180207224623p:plain

参考

https://support.google.com/docs/answer/4492226?hl=ja https://qiita.com/Sasakky/items/09e3bc1536f0569fc893

Angularで画像の遅延読み込み(ng-lazyload-image)

ng-lazyload-imageを使って、スクロールに応じた画像の遅延読み込みをしてみる。
今回はわかり易い例として、スクロールに応じてグリッド配置した画像を例にした。
レスポンシブのグリッドを作るために、AngularMaterialの grid-list も使っています。

デモ: AngularLazyloadGridimageDemo

ng-lazyload-imageの導入

まずは、Angularプロジェクトにインストール

$ npm install ng-lazyload-image --save

app.moduleにインポートしておく。
(別途モジュール分割している場合は、インポートする先が変わってくる)

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { LazyLoadImageModule } from 'ng-lazyload-image'; // 追加
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    LazyLoadImageModule // 追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

グリッド画像に遅延ローディングを設定する

テンプレート側では、<img> タグのlazyLoadディレクティブに画像URLを指定します。
src属性の代わりに、lazyLoadを使うだけなので、使い方はいたって簡単です。

app.component.html

<mat-grid-list cols="4" rowHeight="1:1">
  <mat-grid-tile *ngFor="let item of range(150)">
    <img lazyLoad="assets/images/nattouIMGL3800_TP_V.jpg">
  </mat-grid-tile>
</mat-grid-list>

また、lazyloadの文脈からは外れますが、サンプルの画像をリピートするためのメソッドは、次の記事を参考にAngular 2以降で動作するようにしています。
AngularJS tips - ng-repeat で配列ではなく数値で for ループする方法

app.component.ts

...
export class AppComponent {

  range(n): number[] {
    const arr = [];
    for (let i = 0; i < n; ++i) {
      arr.push(i);
    }
    return arr;
  }
}

ここまでの手順で、スクロールに応じて画像を遅延読込することが出来るのですが、「ふわっ」と徐々に画像を表示させるアニメーションを加えるためのCSSを追加します。
ng-lazyloaded というクラスは、ng-lazyload-imageによって画像が完全に読み込まれたタイミングで動的に付加されるクラスです。

app.component.css

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* lazyload */
  transition: opacity 1s;
  opacity: 0;
}

img.ng-lazyloaded {
  opacity: 1;
}

サンプルのリポジトリ

GitHub - daikiojm/angular-lazyload-gridimage-demo: A sample repository for ng-lazyload-image + Grid list(AngularMaterial)

参考

https://www.pakutaso.com/20180132010nato.html
http://phiary.me/angularjs-ng-repeat-for-loop-with-numbers/
https://www.webcreatorbox.com/tech/object-fit

Angular Animationsを初めて使ってみた

Angularを使ったWebアプリを作り際も、CSSのアニメーションしか使わない場合が多かったのですが、Angular Routerと連携したアニメーションや複数の要素に対するアニメーションなどを実現するためにAngular Animationsを使ってみることにしました。

そもそも今まで触ったことなかったので、気にしてなかったけど、どうやらv4.2以上ではアニメーション関連が強化されているらしい。 今回紹介する内容では、v5.1.1を使っています。

今回は、単一のコンポーネントにごく単純なFade In-Outアニメーションをさせてみたいと思います。 具体的には次のようにぬるっと表示される詳細検索パネル(風)のものです。

f:id:daikiojm:20171223230440g:plain

事前準備

今回は、次のコマンドでサンプルのプロジェクトを作成しました。

$ ng new expansion-panel --inline-style --inline-template

また、app.moduleにBrowserAnimationsModuleをインポートしておきます。

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

...
  imports: [
    BrowserModule,
    BrowserAnimationsModule
  ],
...

検索ボックス

まずは検索ボックスです。
テンプレートには、input要素と、buttonを配置しているだけです。 「詳細」ボタンを押した際に、親のコンポーネントにボタンが押されたことを通知したため、OutputEventEmitterを使っています。  

search.component.ts

import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-search',
  template: `
    <div>
      <form>
        <input type="text">
      </form>
      <button (click)="onClickPanelOpen()">詳細</button>
    </div>
  `,
  styles: [`
    :host {
      position: fixed;
      top: 50;
      z-index: 99;
      left: 50%;
      margin-left: -40%;
      width: 80%;
    }
    input {
      padding: 0;
      font-size: 1.3em;
      font-family: Arial, sans-serif;
      color: #aaa;
      border: solid 1px #ccc;
      margin: 0;
      height: 38px;
      width: 100%;
      background-color: #ffffff;
    }
    button {
      width: 40px;
      font-size: .3em;
      position: relative;
      top: -36px;
      left: 85%;
    }
  `]
})
export class SearchComponent {
  @Output() panelOpen = new EventEmitter<boolean>();
  private panelState = false;

  onClickPanelOpen() {
    this.panelState = !this.panelState;
    this.panelOpen.emit(this.panelState);
  }
}

詳細検索パネル

こちらは、詳細検索パネル(風)です。今回は、アニメーションの例なので詳細検索に関する機能は省いていますが...
親のコンポーネントで見通しが良くなるよう別ファイルにしていますが、特にロジックを持たないコンポーネントです。

panel.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-panel',
  template: `
    <p>詳細検索</p>
  `,
  styles: [`
    :host {
      position: fixed;
      top: 48px;
      z-index: 99;
      left: 50%;
      padding: 0;
      margin-left: -40%;
      width: 80%;
      height: 400px;
      border: 1px solid #cccccc;
      box-shadow: 0px 4px 4px -2px #cccccc;
    }
    p {
      text-align: center;
      margin-top: 180px;
    }
  `]
})
export class PanelComponent { }

app.component

次にapp.componentです。これは、ng newした際に作成されるものを編集して使っています。
アニメーションの定義もこのコンポーネントで行っています。
検索ボックスから受けたpanelOpenイベントに対してonChangePanelState()メソッドをバインドしています。

app.component.ts

import { Component } from '@angular/core';
import { trigger, state, transition, style, animate } from '@angular/animations';

@Component({
  selector: 'app-root',
  template: `
    <app-search (panelOpen)="onChangePanelState()"></app-search>
    <app-panel @panelOpenTrigger *ngIf="panelState"></app-panel>
  `,
  styles: [],
  animations: [
    trigger('panelOpenTrigger', [
      state('void', style({ opacity: 0.4 })),
      state('*', style({ opacity: 1 })),
      transition('* <=> *', animate('0.2s ease-in-out'))
    ])
  ]
})
export class AppComponent {
  panelState: boolean;

  onChangePanelState() {
    this.panelState = !this.panelState;
  }
}
  • テンプレート内の@panelOpenTrigger、triggerのpanelOpenTrigger
    • テンプレート内にトリガーを定義してanimationsのtriggerと関連付けを行っています。
  • state('void', style({ opacity: 0.4 })),
    • アニメーション前の初期状態のstyleを定義しています。 state()の第一引数voidコンポーネントが表示されていない状態を表しています。
  • state('*', style({ opacity: 1 })),
    • アニメーション後のstyleを定義しています。*コンポーネントが表示された際の状態を示しています。
  • transition(' <=> ', animate('0.2s ease-in-out'))
    • transition()メソッドの第一引数には上記のstateに定義した状態の移り変わりを定義しますが、今回は* <=> *で全ての状態の移り変わりをハンドリングするよう設定しています。今回の場合、パネルを開く時、閉じるときで同様のアニメーションを実行します。
    • animate()メソッドで実際のアニメーションを定義しています。ここでは、0.2秒かけてstyleがstate()で定義した状態に変化します。ease-in-outはイージングの種類です。

所感

Angular CDKのExpansion Panelでもそれぽいことができたかもしれない。
そもそもこれぐらいならCSSだけでも出来るかもしれないけど...Angular Animationsの取っ付きとしてはよかったかなと。

参考

AngularCLIで単一ファイルコンポーネント

AngluarCLIでComponentを作成する際にVue.jsの単一ファイルコンポーネントっぽく、1Component1ファイルとする方法を紹介します。

やってみる

コマンド実行時のオプションで指定

コンポーネント作成(デフォルト)

$ ng g c <コンポーネント名>

コンポーネント作成(単一ファイルコンポーネント)

$ ng g c --inline-style --inline-template <コンポーネント名>

--inline-style --inline-templateオプションを付けて実行することでcss、htmlの内容がtsファイルにインラインで記述されます。

※ gはgenerateのエイリアス
※ cはcomponentのエイリアス

.angular-cli.jsonに設定を記述

事前にプロジェクトの設定ファイルに記述しておく方法です。

.angular-cli.json(デフォルト)

...
  "defaults": {
    "styleExt": "css",
    "class": {
      "spec": false
    },
    "component": {}
  }
}

.angular-cli.json(単一ファイルコンポーネント)

...
  "defaults": {
    "styleExt": "css",
    "class": {
      "spec": false
    },
    "component": {
      "inlineStyle": true,
      "inlineTemplate": true
    }
  }
}

以上です。

確認

オプション指定、もしくは.angular-cli.jsonに設定を記述した状態でComponentを作成してみます。

f:id:daikiojm:20171202140127p:plain

作成されたコンポーネントを確認すると、一つのtsファイルにstyleとテンプレートがまとまっているのが分かるかと思います。

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-one-file',
  template: `
    <p>
      one-file works!
    </p>
  `,
  styles: []
})
export class OneFileComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

参考

https://github.com/angular/angular-cli/wiki/generate-component