最近もっぱらJava8 & JavaFX8を勉強中。
JavaFXには色々と標準コントロールがついているけど、
その使い方について、日本語情報があんまり見つかりませんね。
折角調べたのでメモメモ。
LineChartを置く
とりあえずLineChartを置く。
題材はてきとーに、月別のチーム人数を出すグラフと言うことで。
<LineChart fx:id="testLineChart" layoutX="-11.0" layoutY="21.0" prefHeight="464.0" prefWidth="578.0" title="月別チーム人数">
<xAxis>
<CategoryAxis label="Month" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
Paneやら何やらの設定は略
大事なのはxAxisとyAxisで、それぞれ横軸と縦軸の事。以下の二つが設定できる。
- NumberAxis → 名前の通り数値軸(0, 10, 20...のような軸)
- CategoryAxis → 文字列軸(1月、2月...のような軸)
今回は縦軸が人数、横軸が月でどっちもNumberAxisで良いが、
面白くないので横軸は「x月」としてCategoryAxisにした。
データを設定する
このまま画面表示すると空っぽのグラフが出ますが、空っぽの時点でグラフでもないのでデータを設定。
データもべたで設定は面白くないので、以下のてきとーなカンマ区切りテキストファイル(chartData.txt)から読み込み。
01月,10
02月,20
03月,5
04月,9
05月,45
06月,21
07月,9
08月,65
09月,0
10月,11
11月,2
12月,8
今回はControllerクラスのInitializeで設定。
LineChartの折れ線1本分はXYChart.Seriesクラスで表すので作成。
public void initialize(URL location, ResourceBundle resources) {
XYChart.Series<String, Number> series = new XYChart.Series<String, Number>();
series.setName("人数"); // 凡例の名前
凡例はExcelでお馴染みのあれ。
で、ファイルからデータを取って設定。
データを取る部分は本題ではないので、forEachとsplitでざくっと。
try (Stream<String> chartStream = Files.lines(Paths.get("chartData.txt"), StandardCharsets.UTF_8)) {
// 各要素をカンマで区切り、XYChartに設定。設定したXYChartをseriesに追加する。
chartStream.forEach(s -> {
String[] splitResult = s.split(",");
Data<String, Number> xyChart
= new XYChart.Data<String, Number>(splitResult[0], Integer.parseInt(splitResult[1]));
series.getData().add(xyChart);
});
} catch (IOException e) {
e.printStackTrace();
}
// Data設定されたSeriesをLineChartに追加する。
testLineChart.getData().add(series);
本題が分かりづらいですが、大事なのは以下だけ。
折れ線中のデータ部分はXYChartで表す。
Data<String, Number> xyChart = new XYChart.Data<String, Number>(splitResult[0], Integer.parseInt(splitResult[1]));
series.getData().add(xyChart);
testLineChart.getData().add(series);
SeriesのgetData()で取れるObservableListにXYChartを設定してあげれば良い。
最後にLineChartに同じようにSeriesを設定する。
↓こんな感じのグラフに。0のデータの○がはみ出るのはどうにかできるのかな。
また特に指定してないけど、縦軸・横軸の最大は勝手に設定される。
マウス位置のデータ値を表示する
もうちょっと機能が欲しいので、
マウスをあわせたら、その位置のデータ値が出るようにしてみる。
調べた感じ、以下のように単純にマウスイベント設定では駄目
// LineChartそのものにMouseMoved設定
testLineChart.setOnMouseMoved(ev ->{/* ~~~ */});
LineChartは軸の部分や凡例の部分も含まれるので、
チャート部分のマウス位置がちゃんと取れないらしい。
なので以下のようにlookupでチャート背景部分のNodeを取れば良い。
Node chartBackground = testLineChart.lookup(".chart-plot-background");
マウス位置のデータ値は、各AxisのgetValueForDisplayメソッドで取得できるので、イベントはこんな感じ。
(dataLabelはただのLabelです)
chartBackground.setOnMouseMoved(ev -> {
String pointXAxis = testLineChart.getXAxis().getValueForDisplay(ev.getX());
Number pointYAxis = testLineChart.getYAxis().getValueForDisplay(ev.getY());
String dataLabelText = String.format("xAxis:%s, yAxis:%.2f", pointXAxis, pointYAxis);
dataLabel.setText(dataLabelText);
});
ついでにチャート部分でのマウスカーソルが矢印だとかっこ悪いので、
クロスヘアに変えてみた。
chartBackground.setCursor(Cursor.CROSSHAIR);
やったぜ。
setMouseTransparent
実はこのままだと、折れ線上や○の部分でデータ値が出なかった。マウスカーソルも戻っちゃう。
それもそのはずで、折れ線の部分は.chart-plot-backgroundでは無いのでイベントが適用されない。
全部のスタイルに適用するのも大変なので、以下のようにして
LineChart上の.chart-plot-background以外の要素にsetMouseTransparent(true)してあげると良いみたい?
chartBackground.getParent().getChildrenUnmodifiable().stream().filter(
n -> n != chartBackground).forEach(
n -> n.setMouseTransparent(true));
testLineChart.getChildren~でも良さそうだけど。
この設定をしてやると、そのNodeのマウスイベントは無効になって、その下のNodeのイベントが呼ばれるようになる。
とりあえずやりたいことは出来たので満足。