プロジェクト

全般

プロフィール

JavaFXとグラフ(Chart)

見栄えの制御

Chartでは、見栄えを変更するにはたいていはCSSを記述します。

散布図(ScatterChart)

デフォルトでは、次の様に大きな丸のシンボルで散布図が描画されます。

散布図デフォルトのシンボル

このシンボルの形状を変更するには、CSSを記述します。一律変更するには、セレクターに .chart-symbolを指定して記述します。
プロパティ名は、デフォルトの定義をしている modena.css を調べるのが手っ取り早いです。

modena.css のデフォルト定義 変更例
.chart-symbol { /* solid circle */
    -fx-background-color: CHART_COLOR_1;
    -fx-background-radius: 5px;
    -fx-padding: 5px;
}
.chart-symbol {
    -fx-background-radius: 1px;
    -fx-padding: 1px;
}

変更後は次の様に小さな丸のシンボルで散布図が描画されます。

散布図デフォルトのシンボルの大きさを小さく

背景色

LineChartの背景色

.chart {
    -fx-background-color: darkgrey;
}
.chart-plog-background {
    -fx-backgroundcolor: darkgrey;
}

凡例(Legend)のカスタマイズ

凡例(Legend)の形状を変更する

凡例(Legend)のデフォルトは、対応するグラフの色を枠線色とし中が白塗りの円です(下図参照)。

デフォルトの凡例シンボル

折れ線グラフでグラフのシンボルを非表示としている場合、凡例のシンボルは線としたいところです。

線の凡例シンボル

凡例のシンボルの形状と色はCSSで変更可能です。
形状は、CSSでセレクタに.chart-legend-item-symbolを指定して記述します。

.chart-legend-item-symbol {
    -fx-background-radius: 0;
    -fx-background-insets: 0;
    -fx-shape: "M0,5 L0,7 L12,7 L12,5 Z";
    -fx-scale-shape: false;
}
  • デフォルトの塗りつぶしは背景の形状を-fx-background-radius-fx-background-insetsで制御しているので、これをなしとします(0を指定している)。
  • 線をSVGPathで指定しています。

次に、複数の折れ線を表示したときに、各線の色に合わせて凡例シンボルを表示させるため、色設定をします。

.default-color0.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_1;
}
.default-color1.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_2;
}
.default-color2.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_3;
}
.default-color3.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_4;
}
.default-color4.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_5;
}
.default-color5.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_6;
}
.default-color6.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_7;
}
.default-color7.chart-legend-item-symbol {
    -fx-background-color: CHART_COLOR_8;
}

凡例(Legend)で各グラフ(Series)のNodeを取り出す

凡例は、Legendクラス(com.sun.javafx.charts.Legend)ですが、非公開APIで、Java SE 9以降はモジュール化のアクセス制御でアプリケーション側からは利用できません。
しかし、CSSのセレクターで.chart-legend-itemを指定して検索しNodeとして取り出すことは可能です。

Set<Node> chartLegendItemNodes = chart.lookupAll(".chart-legend-item");

取り出したNodeの実際のクラスを見ると、Label型となっていました。

凡例の各項目をクリックしたら処理を入れる

凡例の各項目をNodeとして取り出し、setOnMouseClickedでイベントハンドラを設定すると、マウスに反応する凡例の項目とすることができます。

Set<Node> chartLegendItemNodes = chart.lookupAll(".chart-legend-item");
chartLegendItemNodes.forEach(
    node -> node.setOnMouseClicked(event -> {
        System.out.println("Legend item is clicked");
    })
);

パフォーマンス

グラフ用データの生成

XYChart.SeriesにXYChart.Dataを入れるときは

LineChartに表示するグラフ用データ(点列)を生成する際、最初はSeriesのgetData().addメソッドをループで呼び出していました。数千個の点列は特に遅いと感じず、数十万個の点列を生成しようとしたところ、数分待っても表示されませんでした。

double[] frequencies = ...;
double[] powers = ...;
XYChart.Series<Double, Double> series = new XYChart.Series<>();
for (int i = 0; i < numData; i++) {
    var datum = new XYChart.Data<Double, Double>(frequencies[i], powers[i]);
    series.getData().add(datum);
}

getDataで得られるListはObservableListなので、addの度にリスナーに通知が行き処理が走るためと思われます。

そこで、別途新規にFXCollections.observableArrayListでObservableListを作り(リスナーが空)、ここにaddで点を全て追加します。
続いて、Seriesのコンストラクタにこのリストを渡します。

double[] frequencies = ...;
double[] powers = ...;
ObservableList<XYChart.Data<Double, Double>> list = FXCollections.observableArrayList();
for (int i = 0; i < numData; i++) {
    var datum = new XYChart.Data<Double, Double>(frequencies[i], powers[i]);
    list.add(datum);
}
XYChart.Series<Double, Double> series = new XYChart.Series<>(list);

大量のデータを間引いて表示

グラフに表示する点が大量にあるとき(例:100万個)、100万個のデータをそのままXYChart.Seriesに突っ込んで表示させるとかなり重たい(無駄な)処理となってしまいます。

クリップボードから画像を追加 (サイズの上限: 1 GB)