2016-02-13

JavaFX: チャートの重ね合わせ

JavaFX で棒グラフ (BarChart) と折れ線グラフ (LineChart) を合わせて表示したい場合、StackPane を用いてそれぞれを重ねて表示することで簡単に実現できます。Jewelsea (John Smith) 氏が GitHub Gist でサンプルを公開していますので、その実行例を紹介します [1]

動作を確認した環境は次の通りです。

  • OS: Fedora 23 (x86_64)
  • Java: Java SE 1.8.0_72 (jdk1.8.0_72-1.8.0_72-fcs.x86_64)
  • IDE: NetBeans IDE 8.1

パレート図(もどき)への応用

StackPane を用いたチャートの重ね合わせを応用して、BarChart の y 軸が 左側、LineChart の y 軸が右側にあるパレート図を描画するサンプルを紹介します。ただし、QC七つ道具のパレート図で定義されている累積比率の折れ線と、プロットの仕方が異なっていることをご了承ください [2]

とある会社で品証部門に在籍していたことがあり、そこには日本の品質管理の手法を大切にする気風がありました。何かの時に R で作成したパレート図を自慢気に披露したところ、これはパレート図ではない、と指摘されて赤面したことがあるのです。それが、ここで紹介するパレート図と同じ書き方でしたので、自戒をこめて(もどき)と入れています。個人的には、定義から逸れているからダメだという議論より、このチャートから何を読み取るべきかが大切だとは思うのですが、間違いを間違いだと認めなければなりません。

リスト:Sample_Pareto.java
package sample_pareto;

import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Sample_Pareto extends Application {

    Boolean first = true;

    BarChart<String, Number> barchart;
    LineChart<String, Number> linechart;

    // X data
    String category1 = "A";
    String category2 = "B";
    String category3 = "C";
    String category4 = "D";
    String category5 = "E";
    String category6 = "Others";
    // Y data
    ObservableList<XYChart.Data> data1 = FXCollections.observableArrayList(
            new XYChart.Data(category1, 34),
            new XYChart.Data(category2, 19),
            new XYChart.Data(category3, 12),
            new XYChart.Data(category4, 9),
            new XYChart.Data(category5, 6),
            new XYChart.Data(category6, 10)
    );
    // Y2 data
    ObservableList<XYChart.Data> data2 = FXCollections.observableArrayList(
            new XYChart.Data(category1, 37.8),
            new XYChart.Data(category2, 58.9),
            new XYChart.Data(category3, 72.2),
            new XYChart.Data(category4, 82.2),
            new XYChart.Data(category5, 88.9),
            new XYChart.Data(category6, 100.0)
    );
    // chart title
    String gTitle = "パレート図のサンプル  ";
    String gXTitle = "カテゴリ";
    String gYTitle = "件  数";
    String gY2Title = "累積比率 (%)";
    // geometry
    double sizeX = 600;
    double sizeY = 500;
    double offsetX = 60;

    @Override
    public void start(Stage stage) {
        // X axis
        CategoryAxis xAxis = new CategoryAxis();
        xAxis.setLabel(gXTitle);

        // barchart
        barchart = createBarChart(xAxis, data1);
        barchart.setTitle(""); // ダミー
        barchart.getStylesheets().addAll(getClass().getResource("BaseChart.css").toExternalForm());
        // linechart
        linechart = createLineChart(xAxis, data2);
        linechart.setTitle(gTitle);
        // set chart width
        updateChartWidth();

        // stackpane for overlay
        StackPane pane = new StackPane();
        pane.getChildren().addAll(barchart, linechart);
        pane.setAlignment(Pos.BOTTOM_LEFT);

        Scene scene = new Scene(pane, sizeX, sizeY);
        scene.widthProperty().addListener(
                (ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) -> {
                    if (!first) {
                        sizeX = (double) newSceneWidth;
                        updateChartWidth();
                    } else {
                        first = false;
                    }
                }
        );
        scene.getStylesheets().add(getClass().getResource("ParetoChart.css").toExternalForm());
        stage.setScene(scene);
        stage.show();
    }

    BarChart<String, Number> createBarChart(CategoryAxis xAxis, ObservableList<XYChart.Data> data) {
        NumberAxis yAxis = createYaxis(0, 90, 10, 5);
        yAxis.setLabel(gYTitle);

        BarChart<String, Number> chart = new BarChart<>(xAxis, yAxis);
        chart.setLegendVisible(false);

        XYChart.Series series = new XYChart.Series(data);
        series.setName("カテゴリ");
        chart.getData().add(series);

        return chart;
    }

    LineChart<String, Number> createLineChart(CategoryAxis xAxis, ObservableList<XYChart.Data> data) {
        NumberAxis y2Axis = createYaxis(0, 100, 10, 2);
        y2Axis.setSide(Side.RIGHT);
        y2Axis.setLabel(gY2Title);

        LineChart<String, Number> chart = new LineChart<>(xAxis, y2Axis);
        chart.setLegendVisible(false);
        chart.setHorizontalGridLinesVisible(false);
        chart.setVerticalGridLinesVisible(false);

        XYChart.Series series = new XYChart.Series(data);
        series.setName("累積");
        chart.getData().add(series);

        return chart;
    }

    NumberAxis createYaxis(double min, double max, int major, int minor) {
        final NumberAxis axis = new NumberAxis(min, max, major);
        axis.setMinorTickCount(minor);
        axis.setPrefWidth(offsetX);
        return axis;
    }

    void updateChartWidth() {
        barchart.setLayoutX(0);
        barchart.setTranslateX(0);
        barchart.setMaxWidth(sizeX - offsetX);

        linechart.setLayoutX(0);
        linechart.setTranslateX(offsetX);
        linechart.setMaxWidth(sizeX - offsetX);
    }

    public static void main(String[] args) {
        launch(args);
    }
}
リスト:ParetoChart.css
.chart {
    -fx-padding: 10px;
}

.chart-content {
    -fx-padding: 10px;    
}

.chart-title {
    -fx-font-size: 16pt;    
}

.chart-plot-background { 
    -fx-background-color: transparent; 
}

.axis-label {
    -fx-font-size: 14pt;
}

.axis {
    AXIS_COLOR: #888;
    -fx-tick-label-font: 12pt system;
    -fx-tick-label-fill: derive(-fx-text-background-color, 30%);
}

.chart-bar {
    -fx-border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4) transparent rgba(0, 0, 0, 0.4);
}

.series0.chart-bar {
    -fx-background-color: lightskyblue;
}

.default-color0.chart-series-line {
    -fx-stroke: #f44;
}

.default-color0.chart-line-symbol {
    -fx-background-color: #f44, #f88;
    -fx-background-radius: 6px;
    -fx-padding: 6px;
}
リスト:BaseChart.css
.chart-plot-background { 
    -fx-background-color: white; 
}

実行例

参考サイト

  1. Uses JavaFX to draw layers of XYCharts.
  2. (株)日科技研:パレート図とは(QC七つ道具)|製品案内

0 件のコメント: