質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

1回答

1070閲覧

JavaFXで(Scatter)Chartの上に,座標を合わせてShapeを描画する方法

ukaznil

総合スコア29

JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2017/09/25 13:36

こんにちは。下記について是非お力を貸してください。

###前提・実現したいこと

タイトルの通り,JavaFXのコード上で以下の要求を満たすグラフ(Chart)を描画したいです。

  • XY平面上で散布図を描画する
  • 散布図と同じグラフ上に,楕円(Ellipse)を描画する

散布図はScatterChartを使っています。
重ねて描画なので,PaneにはStackPaneを使っています。

私なりに正しく動作すると想定して書いたコードが以下です。

public class MyGraph extends Application { public static void main(String[] args) { MyGraph.launch(); } @Override public void start(Stage primaryStage) throws Exception { Stage stage = new Stage(); Axis<Number> axisX = new NumberAxis(); axisX.setLabel("x"); Axis<Number> axisY = new NumberAxis(); axisY.setLabel("y"); ScatterChart<Number, Number> scatter = new ScatterChart<>(axisX, axisY); XYChart.Series<Number, Number> series = new XYChart.Series<>(); series.getData().add(new XYChart.Data<>(-10, -10)); series.getData().add(new XYChart.Data<>(-10, 10)); series.getData().add(new XYChart.Data<>(10, -10)); series.getData().add(new XYChart.Data<>(10, 10)); scatter.setData(FXCollections.observableArrayList(series)); // todo: // 例えば (x,y) = (1,2) を中心に,xradius:3, yradius:2の楕円を上書きしたいが, // 下記のコードだと第4象限に小さな楕円が描画されてしまう。 int xCenter = 1; int yCenter = 2; int xRadius = 3; int yRadius = 2; Ellipse ellipse = new Ellipse(xCenter, yCenter, xRadius, yRadius); ellipse.setFill(Color.BLUE); StackPane stackPane = new StackPane(); stackPane.getChildren().add(scatter); stackPane.getChildren().add(ellipse); stage.setScene(new Scene(stackPane)); stage.show(); } }

下記が出力される画像です。
散布図の表示は問題ないのですが,楕円の描画が上手くいきません。
画面上の座標軸とChart(?)の座標軸が異なっているようで,指定したつもりの座標,__(x,y)=(1,2)__の周辺に描画されていません。

イメージ説明

このような要求がある場合,どのように座標の変換等を行えばいいのでしょうか?
localToParent()localToScreen()getTransforms()などのメソッドも発見しましたが,あれこれ試してみたものの,今のところいい結果が出てきません……。

.

お知恵拝借できると幸いです。
どうぞよろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

期待どおりにするには主に2つ注意する点があります。

###StackPaneの子供の配置方法
StackPaneのリファレンスには以下のように書いてあります。

スタック・ペインでは、各子をサイズ変更してそのコンテンツ領域全体に表示するように試みます。子をスタック・ペイン全体に表示できない(子がサイズ変更可能でないか、最大サイズによりそのようにできない)場合、子はalignmentプロパティ(デフォルト値はPos.CENTER)に基づいて領域内で位置合せされます。

Ellipseはリサイズ不能なノードです。これは親が勝手にサイズ変更するのではなく、自身のradiusX/radiusYプロパティーで大きさを決めるノードなのでそのようになっています。そのためStackPaneはEllipseをリサイズせず、位置はデフォルトである「中央」に配置します。よってEllipseのcenterX/centerYの指定は無効になるわけです。

対処としては子供の位置を勝手に配置しないコンテナを用いるとよいと思います。例えばStackPaneの直下にはPaneを置きそのPaneの下にEllipseを置くとcenterX/centerYの指定どおりの位置に表示させることができます。

###チャートのデータの座標系はStackPaneの座標系とは異なる
JavaFXでは標準でNodeの左上の位置がx=0, y=0となり、xの正方向は右、yの正方向は下です。また長さの単位は画面上のピクセル相当が基準となります。つまり座標系について配慮しなければEllipseはおかしな場所に配置されることになります。

さて、チャート上のデータの座標を画面上の座標へ変換するにはAxis#getDisplayPositionが便利です。ただしこれによって求まる画面上の座標を解釈するには若干注意が必要です。それはこの座標がチャートNodeの座標ではなくチャートを構成するプロット領域の座標になっている点です。プロット領域を表すNodeは
Node plotArea = scatter.lookup(".chart-plot-background")
で求まります。


ということで次のようにすると期待通りの位置・大きさでEllipseを表示できると思います。

java

1... 2ScatterChart<Number, Number> scatter = ...; 3Pane pane = new Pane(); 4StackPane stackPane = new StackPane(scatter, pane); 5stage.setScene(new Scene(stackPane)); 6 7// まず画面を表示させる 8 9stage.show(); 10 11// 画面表示時にチャートのデータ系列の座標位置が 12// 自動調整により確定するので、 13// 具体的な座標計算はこの状態で行う必要がある 14 15Node plotArea = scatter.lookup(".chart-plot-background"); 16double centerXOnPlotArea = 1; 17double centerYOnPlotArea = 2; 18double radiusXOnPlotArea = 3; 19double radiusYOnPlotArea = 2; 20double cx1 = axisX.getDisplayPosition(centerXOnPlotArea); 21double cy1 = axisY.getDisplayPosition(centerYOnPlotArea); 22double cx2 = axisX.getDisplayPosition(centerXOnPlotArea + radiusXOnPlotArea); 23double cy2 = axisY.getDisplayPosition(centerYOnPlotArea + radiusYOnPlotArea); 24Bounds boundsOnPlotArea = new BoundingBox( 25 cx1, cy1, 0, Math.abs(cx2 - cx1), Math.abs(cy2 - cy1), 0); 26Bounds boundsOnPane = pane.sceneToLocal(plotArea.localToScene(boundsOnPlotArea)); 27Ellipse ellipse = new Ellipse( 28 boundsOnPane.getMinX(), boundsOnPane.getMinY(), 29 boundsOnPane.getWidth(), boundsOnPane.getHeight()); 30ellipse.setFill(Color.BLUE); 31// 楕円によりチャートの軸・補助線などが見えなくなるため半透明にしてみた 32ellipse.setOpacity(0.5); 33pane.getChildren().add(ellipse);

蛇足ですが、チャートのプロット領域の範囲は(自動調整の場合)XYChart.Seriesに含まれるデータの座標に影響され変化します。よって画面表示後に動的にデータを変更するようなアプリケーションの場合、プロット領域が変化するようなデータ変更・ユーザー操作の度に上記の座標計算をやりなおす必要がある点にも注意が必要と思います。


補足:前述のプロット領域のノードを求める方法ですが、これはChartノードのSkinに実装されているので単にリファレンスをみても書いてありません。Skinがどのような内部構成でコントロールを実装しているかについてはJavaFX CSSリファレンス・ガイドを参照するとよいと思います。このページのXYChatの項目をみるとCSSクラス:chart-plot-backgroundのノードがプロット領域を構成するものであるといったことがわかります。また今回のようなノード階層と位置関係に悩むことはJavaFXではままあることと思います。そんな場合にお勧めしたいツールにScenic Viewというのがあります。これで実行中のアプリケーションのシーングラフの各ノードのプロパティーが(一々デバッグプリントしなくても)簡単に参照できるので便利と思います。

投稿2017/09/25 23:45

編集2017/09/26 00:03
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ukaznil

2017/09/26 14:28

返信が遅くなりすいません。 頂いたご回答を職場で試したところ,期待通りの動きをしたのでしばらく小躍りしておりました。 「stage#show()してから座標を取得する」「リサイズ不能なノード」という概念があることが驚きでした。このような理解がなかったせいで何度やっても位置が変わらなかったのですね。今後ずっと参考にできるご説明でした,どうもありがとうございます! また,座標系についてのも大変ご丁寧に説明してくださったおかげでイメージがつきました。「座標系が違う」ということは漠然と分かったいたのですが,調べた範囲ではこのようなコードに巡りあえず,なかなか理解が進みませんでした。 チャート領域,プロット領域と異なる座標系があることも注意して,今後に活かしていきます。 リファレンスの見方,ScenicViewの紹介もありがとうございます。自力で解決できるためにも,このようなものもうまく利用していきますね。 今回のケースでは動的にデータを変更(プロット領域が再描画される)するわけではなかったので再計算不要でしたが,今後またハマりそうな点だったので大変ありがたいです。 重ね重ねご回答どうもありがとうございました。助かりました。 // 今回の件でJavaFX怖いと思いかけましたが,お陰様で気後れせず頑張れそうです!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問