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()); }