awkだけでSVGのレーダーチャートを描く、72度ずつ回す座標計算と壊れる入力の記録
このブログの比較記事には、製品ごとに5つの軸で点数を付けたレーダーチャートを載せている。あの図は表計算ソフトやグラフ用のライブラリで描いたものではなく、awkのスクリプト1本が直接書き出したSVGだ。作り始めた頃の制作用の環境には、PythonもNode.jsも入っていなかった。それでもWindowsにGit for Windowsを入れておけば、付属のGit Bashでawkはすぐに使える。図の形はほぼ決まっていて、変わるのは製品名と色と5つの点数だけなので、それなら座標を自分で計算して文字として書き出せばよい、と考えた。 このメモでは、そのスクリプトが何をしているのかを、座標の計算から順に記録しておく。あわせて、点数に想定外の値を渡したときや、タイトルに記号を入れたときに図がどう壊れるかも試した。確認に使ったのはGit Bashに入っているGNU Awk 5.4.0だ。書いてから時間がたつと、どの数字が何のためにあるのかを自分でも忘れてしまうので、その備忘録も兼ねている。 awkとSVG、どちらもテキストを書くだけの道具 awkの名前の「k」に当たるブライアン・カーニハン。2023年に出た第2版の著者にも名を連ねているPhoto: Jw12321 / CC0 awkは1977年に、ベル研究所のアルフレッド・エイホ、ピーター・ワインバーガー、ブライアン・カーニハンの3人が作った言語で、名前は3人の頭文字を並べたものだ。本来は、テキストを1行ずつ読み、欄に分けて集計や整形をするための道具として生まれた。3人による解説書『The AWK Programming Language』は1988年に初版が出て、2023年には35年ぶりに第2版が出ている。その第2版に合わせて、カーニハン自身が手を入れている実装にもUTF-8の扱いが加わった。古い道具ではあるが、いまも手入れが続いている。 一方のSVGは、図形をXMLの文字で記述する画像形式で、W3Cの勧告になったのは2001年だ。円なら中心と半径、多角形なら頂点の座標を並べた文字列を書くだけで、ブラウザがそれを描いてくれる。画像を作るといっても、やることは決まった書式の文字を出力することに尽きる。文字を整形して出すのはawkが最も得意とするところなので、この2つは相性が良い。 ふつうのawkのプログラムは入力ファイルを1行ずつ読むが、レーダーチャートのスクリプトは読むべき入力を持たない。そこで、処理をすべてBEGINブロックに書いている。BEGINだけでできたプログラムは、ブロックを実行し終えるとそのまま終了し、標準入力を待たない。値の受け渡しには-vを使う。-vで渡した変数はBEGINの実行より前に代入されるので、ブロックの中ですぐに使える。ファイル名の後ろに変数=値と書く方法もあるが、こちらはBEGINより後に代入されるので、このスクリプトでは使えない。 awk -v TITLE="製品名" -v COLOR="#2F9E44" -v SCORES="4,3,5,2,4" \ -v LAB0="セットアップ" -v LAB1="操作性" -v LAB2="音質" \ -v LAB3="拡張性" -v LAB4="耐久性" \ -f scripts/radar-chart.awk > static/images/radar/sample.svg SCORESは5つの点数をカンマでつないだ1つの文字列で、スクリプトの中でsplit(SCORES, s, ",")で配列に分ける。splitは分けた個数を返し、配列の添字は1から始まる。座標の計算は軸の番号を0から数えているので、点数を取り出すときはs[i+1]と1つずらしている。 正五角形の頂点は、上から時計回りに72度ずつ ニュージャージー州マレーヒルのベル研究所。awkはここで生まれたPhoto: Gottscho-Schleisner Collection / Public domain レーダーチャートの骨組みは、中心から5本の軸が等しい角度で伸びた形だ。360度を5で割ると72度なので、軸の向きは72度ずつずれていく。最初の軸を真上に置くと、図の見た目が安定する。 ここで気を付けるのが、SVGの座標の向きだ。SVGの原点は図の左上にあり、x軸は右へ、y軸は下へ向かって増える。数学の教科書で見るグラフとはy軸が逆なので、真上を指す向きは角度でいうと-90度になる。もう1つ、y軸が下向きのため、角度を増やしていくと点は画面上で時計回りに進む。教科書の座標なら反時計回りに進むところが、そのまま画面に描くと逆回りになる。真上から時計回りに軸を並べたいので、この性質は都合が良い。 軸ごとの向きは、長さ1の矢印(単位ベクトル)として、x方向にcos、y方向にsinの値を持たせる。i番目の軸の角度は「-90度+72度×i」で、計算すると次の表のとおりになる。 軸 角度 x方向(cos) y方向(sin) 図の上での位置 0 -90度 0.0000 -1.0000 真上 1 -18度 0.9511 -0.3090 右上 2 54度 0.5878 0.8090 右下 3 126度 -0.5878 0.8090 左下 4 198度 -0.9511 -0.3090 左上 いま使っているスクリプトは、この10個の数字を小数点以下4桁で直接書き込んでいる。軸が5本から変わらないなら、毎回三角関数を呼ぶより表を持つほうが読みやすいと考えたからだ。awkには円周率の定数が無いので、自分で計算するならatan2(0, -1)を使う。atan2は点(-1, 0)の方向の角度を返す関数で、その値がちょうど円周率になる。手元で15桁表示すると3.141592653589793で、そこから求めたcosとsinを4桁に丸めると、上の表と1桁も違わなかった。 ...