Java
JFreeChart

JFreeChartでグラフ作成

今更ながら、最近JFreeChartを使ってグラフを生成するプログラムを書く必要があったので、JFreeChartの使い方についてまとめておきます。

概念

サンプルコードだけ見て作り始めると、簡単なグラフならすぐにできるのですが、ちょっと複雑になると、すぐにこれってどこに設定すればいいんだとわからなくなってしまいます。そうならないために、まずはJFreeChartの概念を押さえましょう。

グラフサンプル

Chart

グラフ全体を表します。通常はChartFactoryのメソッドを使ってChartを作成するので、Chartによってグラフの種類が決まるように思いがちですが(グラフの種類を決めるのはRendererです)、Chart自身の直接の持ち物は、タイトルと凡例(Legned)、そして後述のPlotのみで、Chartは全体の枠だけのようなものです。

Plot

軸を含むグラフの描画エリア全体がPlotになります。主要な持ち物として、データセット(Dateset)とレンダラー(Renderer)、軸(Axis)などがあります。Plotの種類には、データの持ち方によって、Category PlotとXY Plotの2つがあります。Category PlotにはCategory型のDatasetとRenderer、XY PlotにはXY型のDatasetとRendererしか設定することができません。

Category型

ビジネスで使うようなグラフでは、Y軸は普通の数値ですが、X軸は通常数値ではなくて、商品毎の売り上げグラフであれば、商品Aのような文字列になります。これがCatetoryで、このようなグラフを作成する際にはCategory Plotを使う必要があります。

上記の説明では、わかりやすいようにX軸、Y軸という言葉を使いましたが、グラフによってはX軸とY軸の意味合いが反対になることもあります。JFreeChartでは、軸の名前は通常のX軸の(Categoryを表す)方をDomain Axis、Y軸の方をValue Axisと呼びます。

XY型

よく数学で出てくるような、X軸も通常の数値であるグラフの場合は、XY Plotを使うことになります。

よくある月毎の売り上げのようなグラフでは、X軸が日付になりますが、その場合はCategory Plot、XY Plotのどちらでも表すことができます。前者では2017/7を連続した数値として扱うのではなく、文字列として扱うことになります。実際にはCategory Plotを使う方が多いのはないでしょうか。Category型のレンダラーの方が、棒グラフの種類が豊富にあるからです。

Dataset

表示するデータの集まりがDatasetです。Category型のDatasetでは、X軸=Categoryの値(例:2017年4月)、Y軸の値(例:10)、データの内訳=Seriesがある場合はその値(例:商品A)が1つのデータとなります。

Renderer

Datasetを元に、実際にグラフを描画するのがRendererで、Rendererの種類=グラフの種類になります。どのようなRendererがあるかは、Javadocでorg.jfreechart.chart.renderer.categoryパッケージorg.jfreechart.chart.renderer.xyパッケージのクラスを確認してください。

グラフの見た目をいろいろとカスタマイズするには、Rendererのメソッドを使って設定します。

複合グラフを作成する場合は、グラフの種類毎にRenderer/DatasetのペアをPlotに追加します。

サンプルコード

Category型

最初に示したサンプルグラフを生成するコードです。上記の説明が頭に入っていれば、簡単に理解ができると思います。

JFreeChartSample.java
public class JFreeChartSample {

    public void createChart(ChartType type) { // 棒の部分が積み上げか集合かを、引数で変えられるようにする
        // 基本となる積み上げ棒グラフの作成 Renderer/Dataset#0
        JFreeChart chart = ChartFactory.createStackedBarChart("Title", "Category", "Value", barDataset());
        CategoryPlot plot = (CategoryPlot) chart.getPlot(); // StackedBarChartはCategory型
        String filename = type.getFilename();
        if (type == ChartType.CLUSTERED) { // 集合棒グラフへの変更
            BarRenderer barRenderer = new BarRenderer();
            barRenderer.setItemMargin(0.0); // カテゴリ内のバーの間隔
            plot.setRenderer(barRenderer);
        }
        // 折れ線(合計)の追加
        LineAndShapeRenderer lineRenderer1 = new LineAndShapeRenderer();
        plot.setRenderer(1, lineRenderer1); // Renderer#1
        plot.setDataset(1, lineDataset1()); // Dataset#1
        // 折れ線(前年度比)の追加、軸を別にしたいのでRenderer/Datasetを分ける
        LineAndShapeRenderer lineRenderer2 = new LineAndShapeRenderer();
        plot.setRenderer(2, lineRenderer2); // Renderer#2
        plot.setDataset(2, lineDataset2()); // Dataset#2
        // 軸の追加
        NumberAxis axis = new NumberAxis("前年度比 (%)");
        axis.setRange(0, 200);
        plot.setRangeAxis(1, axis); // RangeAxis#1
        plot.mapDatasetToRangeAxis(2, 1); // Dataset#2はRangeAxis#1を使う
        // 凡例の位置
        chart.getLegend().setPosition(RectangleEdge.RIGHT);
        // 描画順の変更 デフォルト降順(#0が最後)→昇順(#0が最初、後のものが上に表示される)
        plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);

        // 画像の生成
        try {
            ChartUtilities.saveChartAsPNG(new File(filename), chart, 600, 400);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public enum ChartType {
        STACKED("stackedChart.png"), CLUSTERED("clusteredChart.png");
        private String filename;
        ChartType(String filename) { this.filename = filename; }
        public String getFilename() { return filename; }
    }

    private DefaultCategoryDataset barDataset() {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        dataset.addValue(10.0, "商品A", "2017年4月");
        dataset.addValue(15.0, "商品B", "2017年4月");
        dataset.addValue(10.0, "商品A", "2017年5月");
        dataset.addValue(5.0, "商品B", "2017年5月");
        dataset.addValue(5.0, "商品C", "2017年5月");
        dataset.addValue(5.0, "商品A", "2017年6月");
        dataset.addValue(10.0, "商品C", "2017年6月");
        return dataset;
    }

    private DefaultCategoryDataset lineDataset1() {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        dataset.addValue(25.0, "合計", "2017年4月");
        dataset.addValue(20.0, "合計", "2017年5月");
        dataset.addValue(15.0, "合計", "2017年6月");
        return dataset;
    }

    private DefaultCategoryDataset lineDataset2() {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        dataset.addValue(100.0, "前年度比", "2017年4月");
        dataset.addValue(60.0, "前年度比", "2017年5月");
        dataset.addValue(80.0, "前年度比", "2017年6月");
        return dataset;
    }
}