• 作成:

TypeScriptで世界各国の人口を表すインフォグラフィックスを作成するレポートを見直して自己評価

背景

簡単なアドレス帳のレポートを見直してweb技術と自分のwebに対する姿勢の変化を観察する - ncaqの続きです.

2015年06月に記述したレポートを元にしています.

オブジェクト指向技術の第2回レポートでは世界各国の人口を表すインフォグラフィックスを作成するという課題が出ました.

おそらく私以外全員の受講生はネイティブのJavaScriptを使っていたのですが, その時期は私はAltJSにハマっていたので「TypeScriptを使って良いですか?」という問い掛けをして, 許可を貰いました.

なぜTypeScriptなのかと言うと, 当時Fay, Dart, JSX(DeNA)は試したけれど, TypeScriptは試していなかったからです.

この記事も前回と同じく, レポートの内容を改訂して書いているので, 一部不自然な文章になっています. ご了承ください. 本物のレポートには, 先生の課題条件指定文が全文引用で載っているのですが, それをそのまま載せるのは気が引けるんですよね. 引用の範囲に収まるので著作権的には問題ないとは思うのですが.

ソースコード

当時TypeScriptに@typesなんてものは存在しなかったので, referenceを使っています. コンパイル環境を整えるのも面倒くさいので, コンパイル済みのjsも添付しておきました.

同じ場所に全部置いてhtmlを開けば多分実行されます.

世界地図のSVGファイルをダウンロードしweb上に地図を表示する

SVG地図

Wikimedia Commonsの地図を使っています.

そのままmainに追加すると, SVGに設定されていたwidth, height属性により, 空白が生じるため, サイズの属性は削除します.

しかしChromiumだとサイズの属性を削除すると表示されなかったため, CSSで対処しています.

d3.js

d3.jsを使うこと, SVGファイルの読み込みにはD3.jsのd3.xml()を使うこと, という指定があったので, 読み込みにはd3.xml()を使いました.

しかし, 今回の課題では結局d3.jsは読み込み以外に全く使いませんでした. 何回かd3.jsを使おうかとリファレンスやStackOverFlowを読みましたが, 今回の課題の領域では, 結局普通のDOM APIとCSSで出来ることをいたずらにややこしくするだけに思えました.

d3.jsの真の価値は, ツリー表示や折れ線グラフなどのもともと存在するレイアウトを利用できることにあることを知りました. レイアウトを使えば, 複雑な図形がレイアウトに従うだけで描けます. しかし, それはレイアウトに従ったものであるため, 異文化のSVGと組み合わせるのは面倒だと思います. d3.jsは, もともと存在するSVGを利用するのではなく, 新たにグラフを描く能力が高いのではないかと思いました. また, 非プログラマでもサンプルのコードを利用して, データを変えるだけで利用可能というのは強いでしょう. d3.jsのレイアウトをそのまま使っても良い時は, d3.jsを使うかもしれません.

世界各国の人口をThe World Bankから取得する

統計情報

APIの解説はWorld Bankが参考になります.

課題条件には, webブラウザ経由で予めダウンロードしてくれば良いと書いてあったのですが, それはwebアプリとしておかしいと思ったので, そこはこだわってJSONPによる読み込みを行いました. というかWorld Bankはcorsを許可してないんですよね. お前worldに対してapi使用させる気あるのか? と思いながら頑張ってjsonpを学習していました.

というか, いくらなんでもこのAPIはわかりにくいにも程があります. クエリ文字列生成ツールが生成するURLも, 全くルールに一貫性がなく, 謎の略語が頻出します.

このクエリの略語も,

  • POP -> population
  • TOTL -> total

はかろうじてわかりますが, SPとはなんだ? 2文字の略語など山ほどあって意味不明です. totalをTOTLと1文字略して誰が得をするんだろう?

このAPIの設計者はプログラミング作法を読むべきです. グローバル空間の名前は省略するべきではありません.

この課題には全く関係ないのですが, F#にはworld bank用の大変わかり易いライブラリがあって, いいなー羨ましいなーと思いながら見ていました. F# Data: WorldBank プロバイダー

jsonp

jQuery.ajaxjQuery.getJSONはJSONPに対応しています. なので, これを使えばJSONPでworld bankからデータを取得できると最初に私は考えました. しかし, jQueryの関数を普通に使っても, world bankからデータは取得出来ないんですね.

world bankのAPIは, JSONPのコールバック関数名として渡されたクエリ文字列を全て小文字に変換します. そのためjQuery.get80123847040のような関数名のQを小文字変換し, jquery.get80123847040に変換するため, そんな関数名はないというエラーが発生します.

まあ, そういう時のために, jQuery.ajaxのオプションにはコールバックの関数名指定できるのですが, それを使うと無名関数をコールバックとして扱えないのが気になって, JSONPでデータを取得する関数loadJsonpを自作しました.

これは乱数で決めたグローバル関数にコールバックを代入します. 衝突の可能性を減らすために乱数の幅を大きくしたいところですが, worldbankのサーバは長い関数名を勝手に省略してくるため, あまり長くするとエラーになります. なんでworld bankのapiはこんな仕様なんでしょうかね.

国家以外のデータを排除

国家のみのIDを取得する必要があったので, ISOのページからISO-3166-1のデータをダウンロードしようとしたのですが, データへのアクセスがメンバー限定でした.

なので仕方なく, ISO 3166-1 - Wikipediaで開発者ツールを開いて, JSON.stringify(Array.reduce(document.querySelectorAll(".sortable td:nth-child(5)"), (a, e) => {a.push(e.innerHTML); return a;}, [])) してJSONを抽出しました.

当時のTypeScriptではES7のSetが使えなかったので, 配列に線形アクセスするしかないのが気掛かりでした.

インフォグラフィックスを作成する

課題条件では, 地図の上に人口を表す円を12カ国以上の国に指定すればよかったのですが, 国を特定するのが面倒だったので全ての国で表示しました.

この課題は飛び地のある国の中心座標を求めるのが難しく, 正しい答えは人によって異なるのでしょう. 私は最も大きい面積の子要素の中点を求めることにしました. 本島ということですね.

getBBoxを使えば面積が求められます.

これで8割方解決するのですが, グリーンランドやチリなどは変な位置になってしまいます. これは地図SVGの座標データがヘンなことになっているからなので, まあ良いことにしました.

円を追加する(ミス)

円グラフ

ここで私は痛恨のミスをしまして, 円なのに人口を単純に半径に反映させてしまいました. 円の面積はπ×R2π×R^2で求められるので, RRにそのまま反映させるべきではありませんでした. sqrtsqrtなどの処理を行うべきでした.

レポートの発表では「インフォグラフィックス界隈は, こういう時によく対数表示などを行って他の差を可視化させようとするが, 私はそういった行為は「騙し」であり, わかりにくいものには「わかりにくい」という情報があり, それは取り去ってはいけない「わかりにくさ」であると考えている」などと嘯きましたが, そんなものは単なる負け惜しみでした.

私が円グラフが嫌いで, 人は円の面積を正しく比較できないという説を信用していますが, このインフォグラフィクスというレギュレーションにおいては, 面積は人口に正比例させるべきでした.

レポートの発表会でインフォグラフィクスのことを貶したことを私は相当後悔しています. ソフトバンクやナイチンゲールなどが使うクソグラフが嫌いなのは今でも変わりませんし, そんなものより数値や比を直接見たほうが良いと思っているのは変わりませんが, インフォグラフィクスを全て批判するべきではありませんでした. 私は今もバカですが, 2015年の私はもっとバカでした.

別のインフォグラフィックスを作成する

大学入学から3回生になるぐらいまでの期間, 私は強烈な円グラフアンチで, 棒グラフはまだマシだと考えていました. 今となってはなんであんなに円グラフに憎悪を燃やしていたのかよくわかりません. 他の講義でも他人の円グラフを批判していて思い返すと痛々しい.

棒グラフ信者だったので, world bankの国別の年代別人口データを使用して, 各国のテキストをクリックすると, その国の年代別の人口を棒グラフにして表示するようにしました.

サークルだとは国同士で被っているのでクリック領域にはできないので押しにくいですがテキストにします. ただ前のSVG要素の順番のままだと, テキスト領域にサークル領域が被さってクリックできないので, テキスト領域を全て後方に位置させるように変更しました.

こういう順序を気にしないといけないことを, 非同期を混ぜて行うとコールバックが非常にややこしくなるため, loadJsonpなどをcallbackを使う方法からPromiseを活用するように変更しました.

棒グラフ

別の国の名前をクリックするか, Escapeキーを押すと削除されます.

SVGアニメーション

本当は, 棒グラフを表示する前にSVGアニメーションでズームを行うようにするつもりでした. しかし, ズーム自体は単にviewBox属性を操作するだけなので簡単ですが, アニメーションを繰り返し実行するとdelayなしにズームしてしまったり, CSSアニメーションのようにイベントリスナを設定できなかったりして, 時間的に間に合わなかったのと, 棒グラフという本質から関係がないことに気が付いたので, 削除しました. <animate>タグでviewBox属性を操作したのが問題であって, transfrom関係のAPIを使えば問題なかったと考えています. しかしtransform関係のAPIはSVGの行列変換の仕様について学ぶ必要があるため, 大変ややこしいとの評判です.

というのが2015年06月の文章です. その直後の2015年07月の翻訳記事SVGアニメーションの現状 | プログラミング | POSTDを読んでSMILが死に向かっていることを知って愕然としました. 2017年でもEdgeは頑なにサポートしようとしませんしやっぱり死にましたね…

今はCSSで全部どうにかしよう派です. reactとインラインSVG扱えますし…

Promise最高

Promiseを使うと, コールバックの連なりをコンパクトに記述出来るだけでなく, 面倒なjsonpのコールバックをオブジェクトに包んで柔軟に使用できます. Promiseは単純なオブジェクトとして使用できるため, 配列に包むのも, 所有するのも自由で, 評価順序なども簡単に変更できます.

それでPromise最高と思っていたので, 最近のasync, awaitをむやみにありがたがってる風潮は微妙に思っています.

当時のTypeScriptに対する誤解

当時はTypeScriptに対してこんな誤解を抱いていました. 当時としては誤解ではないものもあります.

JavaScriptからあまり進歩していない, ES7の機能が標準で使えずES7と比べるとむしろ後退している

今はES2015のコンパイルオプションも充実し, ストリングリテラル型, ジェネリクス, 非null強制などのたくさんの機能が入り, 強くなりました.

標準ライブラリの型定義が不足してる上に導入が面倒

@typesの導入で簡単に導入できるものがたくさん増えました.

結局キャストやany型を使用することになる

実はTypeScriptではキャストをあまり使わなくて良いんですね. instanceofifを使えば, タイプガードでスコープごとに型を確定させてくれるので, キャストを使わなくても良いのです. any型を使ってもこの機能があれば安心ですね.