JavaFXとグラフ(Chart)¶
見栄えの制御¶
Chartでは、見栄えを変更するにはたいていはCSSを記述します。
散布図(ScatterChart)¶
デフォルトでは、次の様に大きな丸のシンボルで散布図が描画されます。
このシンボルの形状を変更するには、CSSを記述します。一律変更するには、セレクターに .chart-symbol
を指定して記述します。
プロパティ名は、デフォルトの定義をしている modena.css を調べるのが手っ取り早いです。
modena.css のデフォルト定義 | 変更例 |
---|---|
|
|
変更後は次の様に小さな丸のシンボルで散布図が描画されます。
背景色¶
LineChartの背景色¶
.chart {
-fx-background-color: darkgrey;
}
.chart-plog-background {
-fx-backgroundcolor: darkgrey;
}
グラフの描画¶
表示範囲¶
数値(NumberAxist)の範囲を指定する¶
setLowerBound、setUpperBoundで軸の表示範囲(下限、上限)を指定可能です。
ただし、NumberAxisのAutoRangingプロパティが有効だと、下限・上限の設定によらず、描画するグラフのデータに基づいて表示範囲が決まります。
凡例(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に突っ込んで表示させるとかなり重たい(無駄な)処理となってしまいます。