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

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

ただいまの
回答率

90.86%

  • Java

    12807questions

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

  • JavaFX

    372questions

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

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

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 480

ukaznil

score 19

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

前提・実現したいこと

タイトルの通り,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()などのメソッドも発見しましたが,あれこれ試してみたものの,今のところいい結果が出てきません……。

.

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

期待どおりにするには主に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を表示できると思います。

...
ScatterChart<Number, Number> scatter = ...;
Pane pane = new Pane();
StackPane stackPane = new StackPane(scatter, pane);
stage.setScene(new Scene(stackPane));

// まず画面を表示させる

stage.show();

// 画面表示時にチャートのデータ系列の座標位置が
// 自動調整により確定するので、
// 具体的な座標計算はこの状態で行う必要がある

Node plotArea = scatter.lookup(".chart-plot-background");
double centerXOnPlotArea = 1;
double centerYOnPlotArea = 2;
double radiusXOnPlotArea = 3;
double radiusYOnPlotArea = 2;
double cx1 = axisX.getDisplayPosition(centerXOnPlotArea);
double cy1 = axisY.getDisplayPosition(centerYOnPlotArea);
double cx2 = axisX.getDisplayPosition(centerXOnPlotArea + radiusXOnPlotArea);
double cy2 = axisY.getDisplayPosition(centerYOnPlotArea + radiusYOnPlotArea);
Bounds boundsOnPlotArea = new BoundingBox(
  cx1, cy1, 0, Math.abs(cx2 - cx1), Math.abs(cy2 - cy1), 0);
Bounds boundsOnPane = pane.sceneToLocal(plotArea.localToScene(boundsOnPlotArea));
Ellipse ellipse = new Ellipse(
  boundsOnPane.getMinX(), boundsOnPane.getMinY(),
  boundsOnPane.getWidth(), boundsOnPane.getHeight());
ellipse.setFill(Color.BLUE);
// 楕円によりチャートの軸・補助線などが見えなくなるため半透明にしてみた
ellipse.setOpacity(0.5);
pane.getChildren().add(ellipse);

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


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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/26 23:28

    返信が遅くなりすいません。
    頂いたご回答を職場で試したところ,期待通りの動きをしたのでしばらく小躍りしておりました。

    「stage#show()してから座標を取得する」「リサイズ不能なノード」という概念があることが驚きでした。このような理解がなかったせいで何度やっても位置が変わらなかったのですね。今後ずっと参考にできるご説明でした,どうもありがとうございます!

    また,座標系についてのも大変ご丁寧に説明してくださったおかげでイメージがつきました。「座標系が違う」ということは漠然と分かったいたのですが,調べた範囲ではこのようなコードに巡りあえず,なかなか理解が進みませんでした。
    チャート領域,プロット領域と異なる座標系があることも注意して,今後に活かしていきます。
    リファレンスの見方,ScenicViewの紹介もありがとうございます。自力で解決できるためにも,このようなものもうまく利用していきますね。

    今回のケースでは動的にデータを変更(プロット領域が再描画される)するわけではなかったので再計算不要でしたが,今後またハマりそうな点だったので大変ありがたいです。

    重ね重ねご回答どうもありがとうございました。助かりました。

    // 今回の件でJavaFX怖いと思いかけましたが,お陰様で気後れせず頑張れそうです!

    キャンセル

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

  • ただいまの回答率 90.86%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    JavaFXでダブルバッファリングのやり方が分からない

    JavaFXでダブルバッファリングのやりかたが分かりません。 BufferedImageクラスとImageIOクラスを使ってイメージに書き込んで それをSwingFXUti

  • 解決済

    Labelが表示できません!

    前提・実現したいこと JavaFXでスプラッシュウィンドウを作ろうと思っています. しかし,実行してみたところ,Labelが表示されません. 発生している問題・エラーメッ

  • 解決済

    TableViewの複数選択をマウスだけでやりたい

    TableViewを複数選択させるのにCTRLキーを押しながらクリックする必要がありますが、 CTRLキーを押さなくても選択や選択解除したいと思っています。 これを実現する

  • 解決済

    JavaFXのチャートのデータにクリックイベントを追加したい

    前提・実現したいこと JavaFXでチャートを描画しています。 チャート・・・例えばScatterChartに描画したデータ点に何らかの形でクリックイベントを追加したいです。

  • 解決済

    長文を短くシンプルにまとめる方法

    この長い長文を短いシンプルにまとめるにはどのようにしたらよいのでしょうか? また、この64個の中から60個目以降のボタンを押すとGUI自体が終了するような処理は可能でしょうか?

  • 解決済

    javafxのGUI作成。画面遷移がうまくいかない問題

    ただいま画面遷移の練習をしています。 下記に記述したコード1単体だと 『スタート』を押すと『タイトルに戻る』に遷移され、画面遷移がしっかりと出来ているのですが、コード1とコード2を

  • 解決済

    AnimationTimer()による画面の分岐

    前提・実現したいこと AnimationTimer()でサイクルをループさせたい 1.1つ目のボタンの表示 2.ボタン1(画像)が5回押されたら3に移行 (ボタン1が押され

  • 解決済

    TableViewで特定のセルのみ編集可能にしたい

    Java8 JavaFX でプログラムを作成しています。 TableViewで列の設定は編集不可にしている状態で 特定のセルのみ編集可能にする方法はありますでしょうか? ご

同じタグがついた質問を見る

  • Java

    12807questions

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

  • JavaFX

    372questions

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