HAKUTAI Tech Notes

IT関連、数学のことなどを主として思いつきで色々書き溜めていきます

AngularとPaper.jsで簡易なデジタイザを作る5

概要

ここまでデジタイザを作ってきたが、そもそもデジタイザというものはプロットによって明らかにした座標の数値データを何かに利用するためにあるのだから、画面上でプロットしたデータをファイル出力して扱える形にしないとあまり意味がない。

x, y座標を表示しているテーブル部分をドラッグ&コピーして適当にエクセルなどに貼り付ければ、一応数値として取り出すことは可能だ。しかし、そんな手作業をいちいちやるのは面倒くさすぎるので、プロットした座標データを一発でファイル出力できるようにしてみる。

完成イメージ

  • プロットした座標値をファイル出力する f:id:rozured:20210625170123p:plain

実装

CSV出力処理

はじめに、コンポーネントのメンバ変数として出力ファイル名を定義しておく。outFileNameという変数を定義して適当な名前を設定する。

outFileName = 'coordinate.csv';


コンポーネントのクラスファイルに追加するメソッドは1つだけ。CSV出力処理を行うexportCSV()を定義する。

exportCSV()

まず、座標点が一点もプロットされていない状態だと当然ながら出力するものがないので、その場合はメッセージを表示して以降の処理はスキップする。

出力するCSVファイルは、データの情報(列名)を示すheader部分とデータ本体のbody部分をくっつけて出力する。 CSV形式はデータをカンマ,で区切る。データ区切りの記号を指定する変数としてdelimiterを用意し,を格納する。 また、座標点のx, yの前に通し番号をつけて表示することにする。

header
データの内容が分かるように通し番号No、座標値XYという列名を表示する。 'No, X, Y\n'のようにそのまま文字列を書いてもいいが、せっかくdelimiterがあるので列名を入れた配列をdelimiterjoinしている。行末には改行コード\nを入れる。

body
座標点を格納している配列であるvertexListの各要素についてx座標、y座標の値を取り出して1行分のデータを作っていく。なお、vertexListxyをkeyに持つオブジェクトを1つの要素とした配列になっている。

1つ目のmap()の内部ではvertexListの各要素vertexについてObject.keys(vertex)でkeyの配列[x, y]を取得し、さらに2つ目のmap()でkeyに対応するvalueを取得している。今回は座標値を小数点以下3桁まで表示するものとして、toFixed(3)で小数点以下の桁数を指定している。「通し番号, x座標, y座標」の形ができたら最後に改行コード\nを入れて1行分のデータが出来上がる。


このheaderbodyを結合したcsvStringが、出力するCSVデータの中身そのものになる。


後は一般的なファイル出力の手順を踏むだけだ。

まず、データ文字列とデータタイプ(text/csv;charset=utf-8)を指定してBlobオブジェクトを生成する。 生成したBlobオブジェクトをwindow.URL.createObjectURL(blob)でurlに変換する。

次に、ファイルをダウンロードするためのリンクとなるHTMLAnchorElementaタグ)をdocument.createElement('a')で生成する。 生成したHTMLAnchorElement要素のhref属性にダウンロード用urlを、download属性にファイル名を設定する。 HTMLAnchorElement要素自体は画面上に見せないものなのでvisibilityhiddenにする。 後はdocument.body.appendChild(link)HTMLAnchorElement要素を追加してクリックすることでファイルがダウンロードされる。 ダウンロード後はHTMLAnchorElement要素を除いておく。

  exportCSV(): void {
    if (this.vertexList.length === 0) {
      alert('座標点がプロットされていません。');
      return;
    }

    const delimiter = ',';
    const header = ['No', 'X', 'Y'].join(delimiter) + '\n';
    const body = this.vertexList.map((vertex, index) => {
      return index + delimiter + Object.keys(vertex).map(key => {
        return vertex[key].toFixed(3);
      }).join(delimiter);
    }).join('\n');

    const csvString = header + body;
    const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;'});
    const url = window.URL.createObjectURL(blob);

    const link: HTMLAnchorElement = document.createElement('a');
    link.setAttribute('href', url);
    link.setAttribute('download', this.outFileName);
    link.style.visibility = 'hidden';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }


次にhtmlの方を編集する。 「CSV出力」ボタンを追加してclickイベントとexportCSV()を紐付けるだけでいい。 画像ファイルが読み込まれていない時だけボタンを非活性にしている。

      <div class="d-flex image-configure mt-2">
        <div class="col-10 p-0">
          <button mat-raised-button (click)="onClickFileInputButton()">画像を読み込む</button>
          <button mat-raised-button (click)="setAxisRange()" class="ml-2" [class.editingAxis]="isEditAxis"
            [disabled]="!file || vertexList.length !== 0">
            {{ isEditAxis ? '座標軸の設定終了' : '座標軸を設定する' }}
          </button>
          {{vertexList.length}}{{vertexList.length !== 0}}
          <button mat-raised-button class="ml-2" [disabled]="!file" (click)="resetViewConfig()">Viewをリセット</button>
          <button mat-raised-button class="ml-2 export-csv" [disabled]="!file" (click)="exportCSV()">CSV出力</button>
        </div>
      </div>

おわりに

以上で、長きに渡ったデジタイザを作る話は終わりとなる🎉🎉🎉
これまで実装してきたコードはこちら


参考