...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

DFL: 折れ線グラフを描くサンプルコード

std.csvをデータモデルとして受け取って、折れ線グラフを描くLineGraphRendererクラスを、dfl.chartモジュールに追加しました。

TableRendererに負けず劣らず、折れ線グラフの見かけを自由に変更できるようにしています。

CSVの要件

上図中のコードに示したものが、同図の大きなグラフの元になっているCSVです。

LineGraphRendererに取り込むCSVは、ヘッダー行を必須としました。 1行目はすべて文字列として読み込まれます。 もしCSVにヘッダー行がなくてカラムの型の不一致があった場合はエラーになるので、 CSVには必ずヘッダー行を付けます。

LineGraphRenderer!(T...)に与えるテンプレートパラメータTは、ヘッダー(1行目)を除いたレコード部分(2行目以降)のカラムの型を列挙します。

横軸ラベルがあるCSV

上図のCSVは、1列目が横軸のラベルになっています。 そういうCSVを描画したいときは、1つ目のテンプレートパラメータをstring型にします。 intの1つ分が系列1つ分になるわけですが、デフォルトで用意してある色数の制約により、 最大16個(横軸のラベルの分を除く)まで列挙できます。

alias CustomLineGraphRenderer = LineGraphRenderer!(string,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int);
CustomLineGraphRenderer _graph;

横軸ラベルがないCSV

string csv2 =
    "A,B,C\n" ~ 
    "70,80,80\n" ~ 
    "60,90,80\n" ~ 
    "80,70,80\n" ~ 
    "90,60,80\n";

1列目が横軸のラベルではなく、1列目からデータが入っているCSVの場合は、 テンプレートパラメータにカラムの型をそのまま列挙します。 こちらの場合でも、最大16個まで列挙できます。

alias CustomLineGraphRenderer2 = LineGraphRenderer!(int,int,int);
CustomLineGraphRenderer2 _graph2;

記事冒頭図中の小さなグラフは、このタイプのCSVをマージンだけ設定変更して描画したものです。

コンストラクタ呼び出し

auto graph1 = new LineGraphRenderer!(int,int,int)(/+string+/csv2, /+int+/numRecords); // (1)
auto graph2 = new LineGraphRenderer!(int,int,int)(/+string+/csv2, /+int+/firstRecord, /+int+/lastRecord) // (2)

(1)のとおり、1つ目の引数にstring型を、2つ目の引数にレコード数(ヘッダー行を含まない)を与えます。 コンストラクタをstring引数1つで呼び出してから、this.firstRecord=0、this.lastRecord=numRecords-1とするのと同義です。

当初、コンストラクタ呼び出し後にthis.firstRecordとthis.lastRecordの設定をしないと何も表示されなかったのですが、 必須だったらコンストラクタで与えるべきと思い、一番基本的な用法となるだろう(1)を追加したうえで、 (2)の3引数のコンストラクタを増やしました。

TableRendererにも同様のコンストラクタを追加しました。

サンプルコード

同じCSVをTableRenderにより表形式で描画しているコードをコメントアウトしてあるので、 コメントアウトを取れば表も描画されます。

import dfl;

version(Have_dfl) // For DUB.
{
}
else
{
    pragma(lib, "dfl.lib");
}

class MainForm : Form
{
    alias CustomLineGraphRenderer = LineGraphRenderer!(string,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int);
    CustomLineGraphRenderer _graph;

    alias CustomLineGraphRenderer2 = LineGraphRenderer!(int,int,int);
    CustomLineGraphRenderer2 _graph2;

    // alias CustomTableRenderer = TableRenderer!(string,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int);
    // CustomTableRenderer _table;

    // alias CustomTableRenderer2 = TableRenderer!(int,int,int);
    // CustomTableRenderer2 _table2;

    public this()
    {
        this.text = "LineGraphRenderer example";
        this.size = Size(1000, 800);
        string csv =
            "教科,山田,佐藤,井上,田中,木下,藤原,山本,大森,伊藤,高橋,鈴木,中村,小林,松井,木村,近藤\n" ~ 
            "国語,70,80,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n" ~ 
            "算数,60,90,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n" ~ 
            "理科,80,70,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n" ~ 
            "社会,90,60,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n";
        _graph = new CustomLineGraphRenderer(csv, 4);
        _graph.showLegend = true;
        _graph.legendLineHeight = 18;
        _graph.chartMargins = ChartMargins(50, 50, 50, 50);
        _graph.plotPointSize = 10;
        _graph.verticalZeroPosition = VerticalZeroPosition.BOTTOM;
        _graph.plotAreaAndLegendSpanX = 50;
        _graph.plotAreaAndHorizontalScaleSpanY = 10;
        _graph.plotAreaLeftPadding = 20;
        _graph.plotAreaRightPadding = 20;
        _graph.plotAreaHeightOnDisplay = 300;
        _graph.hasHorizontalScale = true;
        _graph.horizontalScaleSpan = 100;
        _graph.horizontalScaleLineInnerSide = 0;
        _graph.horizontalScaleLineOuterSide = 5;
        _graph.horizontalScaleHeight = 12;
        _graph.hasVerticalScale = true;
        _graph.verticalMaxScale = 110;
        _graph.verticalScaleLineOuterSide = 5;
        _graph.verticalScaleLineInnerSide = 0;
        _graph.verticalScaleSpan = 20;
        _graph.verticalScaleWidth = 40;
        _graph.backColor = Color.white;
        _graph.plotAreaBoundsColor = Color.black;
        _graph.plotLineColorPalette[0] = Color.black;
        _graph.plotPointFormList[4..8] = PlotPointForm.CROSS;
        _graph.plotPointFormList[8..12] = PlotPointForm.RECTANGLE;
        _graph.plotPointFormList[12..16] = PlotPointForm.TRIANGLE;
        _graph.relocate = Point(50, 50); // Relocate origin point based on top-left margins.

        string csv2 =
            "A,B,C\n" ~ 
            "70,80,80\n" ~ 
            "60,90,80\n" ~ 
            "80,70,80\n" ~ 
            "90,60,80\n";
        _graph2 = new CustomLineGraphRenderer2(csv2, 4);
        _graph2.chartMargins = ChartMargins(10, 10, 10, 10);
        _graph2.relocate = Point(600, 50);

        // _table = new CustomTableRenderer(csv, 4);
        // _table.location = Point(50, 500);
        // _table.hasHeader = true;
        // _table.showHeader = true;
        // _table.headerLine = true;
        // _table.width[] = 40;

        // _table2 = new CustomTableRenderer2(csv2, 4);
        // _table2.hasHeader = true;
        // _table2.showHeader = true;
        // _table2.headerLine = true;
        // _table2.width[] = 40;
        // _table2.location = Point(680, 200);
    }

    protected override void onPaint(PaintEventArgs e)
    {
        if (_graph)
            _graph.draw(e.graphics);
        if (_graph2)
            _graph2.draw(e.graphics);
        // if (_table)
        //     _table.draw(e.graphics);
        // if (_table2)
        //     _table2.draw(e.graphics);
    }
}

static this()
{
    Application.enableVisualStyles();
}

void main()
{
    Application.run(new MainForm());
}

DFLのダウンロード

github.com