DISLIN解説②

 解説①に引き続き、本稿ではまだるっこしいことはどうでもいいからグラフを描いてみよう、という趣旨の解説を行う。ちなみに③の予定はない。私がDISLINに愛想を尽かし、使うことを諦めてしまったからだ。
 というわけで、ありがちなsin、cos関数をプロットしてみることにする。次のサンプルコードは、タイトルや軸のラベルなどややこしいものは全部排除して、最小限の関数呼び出しのみでグラフを描くものである。DISLIN公式のExpample1を元にしている。

int main()
{
    int n = 100, i, ic;
    double fpi = 3.1415926 / 180.0;
    double step = 360. / (n - 1);
    double xray[100], y1ray[100], y2ray[100];

    for (i = 0; i < n; i++)
    {
        xray[i] = i * step;
        double x = xray[i] * fpi;
        y1ray[i] = sin(x);
        y2ray[i] = cos(x);
    }

    Dislin g;
    g.metafl("cons");
    g.disini();

    g.graf(0.0, 360.0, 0.0, 90.0, -1.0, 1.0, -1.0, 0.5);

    g.curve(xray, y1ray, n);
    g.curve(xray, y2ray, n);
    g.disfin();

    return 0;
}

 これを実行すると、何かウィンドウが現れ、次の画像のようなものが表示されるはずだ。 f:id:thayakawa:20191012225049p:plain:w500

 背景が黒い!フォントが汚い!曲線がどちらも白色で区別できない!軸のラベルは!?タイトルは!?tics少なくね!?
 安心してほしい。修正可能だ。まずは上のコードについて順を追って説明していこう。

Dislin g
 単なる宣言である。流石に誰も躓くまい。

g.metafl("cons")
 恐ろしいことに早速意味不明な関数が登場してしまった。いやいや、何のことはない、これは単にグラフの出力先を指定しているだけである(関数名はmetafileの意味だと思われる)。"cons"や"xwin"、"gl"などを与えるとさきほど同じようにウィンドウにグラフを表示する。他にも"png"や"pdf"、"eps"などを指定すればその形式の画像ファイルを生成してくれる。その他の対応形式はオンラインマニュアルの6.1.4を参照。
 なお後述するsetfil関数でファイル名を指定しなかった場合、作成されるファイルはdislin.(拡張子)となる。

g.disini()
 初期化関数。前記事を思い出してほしいのだが、この関数を呼び出すとdislinが初期化され、levelが0から1へと移行し、改めてグラフやキャンバスの設定を行っていく段階に入る。コンストラクタみたいなものだ。

g.graf(double xmin, double xmax, double xor, double xstep, double ymin, double ymax, double yor, double ystep)
 これは“XY軸の2次元グラフを描画する”と意思表示し、その軸の範囲などを指定するための関数である。xmin、xmaxなどは軸の上下限、xorは軸の目盛り数値の最初の値で、ここからxstep刻みに数値が表示される。y軸についても同様である。この関数を呼び出すとlevelが2となり、各曲線や散布図などの設定を行う段階となる。
 なお、これ以外にもgrafp、grafrなどの関数があるが、これらはそれぞれ極座標、スミスチャートを描くためのもの。また3次元ないしカラーバー付きグラフを描きたいときなどはまた別の関数が必要である。
 また、グラフのstepやら目盛りやらを全部指定するのが大変だ、という時のために、自動計算する方法がいくらか用意されている。gaxparなどがそれに相当する。とは言っても、あらゆる状況で全パラメータを自動計算してくれるほど便利なものではないらしい。

g.curve(const double* xlist, const double* ylist, int ndata)
 これが曲線のデータを与えている関数である。引数にはそれぞれ、x座標リスト、y座標リスト、データ点の数を与える。curveという関数名ではあるが、これはXY平面上の任意の点をリストで与えるだけのもので、デフォルトでは曲線として描画されるものの、散布図にしたり、点の形状を指定したりすることもできる。その指定方法はincmrkおよびmarker関数を参照。

g.disfin()
 Dislinを終了する。おそらくこの関数によってグラフが出力される。同時に、Dislinは未初期化のlevel0へと移行する。

 さて、これがDislinでグラフを書くための最小限の関数呼び出しである。しかしこのグラフは見栄えとしても酷いので、次の定型文的なコードを追加しよう。いっそこのあたりは、Dislinを継承しコンストラクタなどで自動設定してしまってもいいかもしれない。

g.scrmod("revers")
 level0の段階で呼び出す。scrmodはbackground、foregroundの色を設定する関数で、"revers"は背景を白に、前景を黒にする。デフォルトの逆の色設定なので、多分reverseの意味なんだろう。

g.imgfmr("rgb")
 level0の段階で呼び出す。metaflで指定した出力先がvirt、tiffpngbmp、imageである場合、この指定を行っておかないと8bit色で出力されてしまう。それ以外のフォーマットである場合は指定しなくても問題ないか効果がない。

g.setfil("filename")
 ファイルに出力する場合、その名前を指定する。

g.hwfont() g.ttfont("arial.ttf") g.bmpfnt("COMPLX")、g.helve()他、g.shdchar()
 フォントに関する設定。level1-3いずれかで呼び出す。
 DISLINでフォントを設定する方法はいくらかあって、出力先に応じて指定を変える必要がある。
 ttfontはTrueTypeフォントを直接指定するものだ。引数はttfファイルを直接指定しなければならない。ただし、色々試したもののどうも輪郭線がベタ塗りっぽく潰れてしまい、汚らしい文字にしかならなかった。
 hwfontはハードウェアフォントに設定するもの。xwinまたはconsに出力する場合は随一の美しさとなるが、フォント自体を選択できないのが悩みどころ。
 bmpfntはDISLINが持っているビットマップフォントを表示するためのもの。ラスター画像で文字が潰れてしまうのを避けたい場合に使う。ただしDISLINのパスが正しく通っていないとフォントを見つけてくれないので注意(パスの設定はインストールフォルダのreadmeを参照)。
 psfontはベクター画像用のフォント設定関数で、PDFやPostScriptを使うつもりならこれで設定すればよい。十分に美しい。
 他にも、winfnt(WMFファイルまたはWindowsディスプレイ)、x11fnt(x11ディスプレイ)と、出力先によっても個別にフォント設定関数が用意されている。
 最後に、shdcharはフォントの塗りつぶしである。shaded fontの場合これを呼んでおかないと、フォントは輪郭のみが表示され白抜きになる。

g.color("red")
 曲線の色を指定している。といっても、“曲線の”と理解するのは望ましくない。正確に言えばforegroundの色を指定している。
 DISLINはforegroundとbackgroundの2色のパレットがあり、上述のscrmodもその組み合わせを指定したものだ。グラフの軸や曲線、文字などは基本的にこのforegroundを参照し、その色で描画される。しかし現実にはもちろん、曲線や点ごとに色を変えたいという要求は自然に生じるため、この関数で逐一foregroundを変更しながらプロットするのである。
 サンプルのようにcurveなどの関数を呼ぶ直前に指定しておけば問題ないし、ある特定のcurveやtitleのみ色を変えたいのであれば、

g.color("red");
g.curve(x, y, n);
g.color("fore");

 とすればよい。color("fore")を呼ぶことで、foregroundはscrmodで指定した初期値にリセットされる。  色のリストは6.3.1を参照。また色の名前ではなくRGBで与えたい、カラーテーブルから番号で与えたい、という場合、それぞれsetrgb、setclrという関数が用意されている。同じく6.3.1参照。

g.incmrk(5)
 これは挙動の理解にだいぶ悩んだのだが、どうやらcurve関数に与えたデータ点を線で結びつつ、点5つごとにシンボルを表示せよ、という意味であるらしい。引数を0にすればシンボルが表示されなくなり、負の数(-n)にすれば線が表示されなくなった上でn点ごとにシンボルが表示される。つまりこの関数で、表示を点にするか、シンボルにするか、線にするか、などを指定するわけである。

g.marker(2)
 シンボルの形状を指定する。例えば2を与えれば三角形になり、20なら塗りつぶされた逆三角形となる。シンボルの形状一覧はDISLINのexamplesに載っている。
 ここで指定したmarkerの形状は、ここでは2と20を指定しているが、いちいち指定しなくてもcurve関数などを呼ぶごとに自動で切り替えていってくれる。適用されるシンボルの番号が1ずつ増えていくのだ。なので、きちんと全曲線にシンボルを指定したい場合はcurveを呼ぶ前に毎回markerを呼べばよいし、形を気にしないのなら呼ばなくてもよい。

g.hsymbl(18)
 シンボルの大きさを指定している。

 ここまでの挙動を理解できれば想像がつくかとは思うが、DISLINは“n番目の曲線や点集団に対して色やシンボル、大きさなどを指定する”というような方法を用いない。基本的には“色やシンボル、大きさなどの現在の設定”がおそらくグローバル変数か何かに格納されており、curveなどの関数が呼ばれたときは“現時点での設定”を参照してプロットするのだ。不可思議な構造である。6文字以内に制限された関数名といい、何というか、全体的にとてもロースペックな環境で動かすことを想定しているかのような、何十年前のライブラリなんだろうと首を傾げてしまうような設計になっている気がする。最初のリリースが1987年とあるので、それなりに古いライブラリには違いないのだが。
 しかしここまでのコードを見ても分かるように、満足のいく図にしたい場合の記述コストはとても大きい。C++を使っているのなら、自分好みの設定を自動的に施してくれるようなラッパーでも作っておくとよいだろう。