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

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

ただいまの
回答率

90.51%

  • JavaFX

    397questions

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

CategoryAxisの拡大

解決済

回答 1

投稿

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

oscikonome

score 9

以下のようなソースにより、チャートをドラッグで範囲指定して拡大、クリックして戻す、を実装しています。

実際はデータが多く、見づらくなるため拡大したいのですが、
これをX軸をCategoryAxisにした場合でも同様に拡大したいのです。
Y軸にだけ拡大を実装することはできるのですが、X軸がそのままなので密になっている部分をピンポイントで拡大して見ることができていません。

ドラッグで直接拡大できずとも、何かしらうまい具合の操作で拡大して密な部分を表示する方法があれば教えていただければと思います。

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Test6 extends Application {

@Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("X軸");
yAxis.setLabel("Y軸");

//creating the chart
final LineChart<Double, Double> lineChart = new LineChart(xAxis, yAxis);
lineChart.setLegendVisible(false);
ObservableList<XYChart.Series<Double, Double>> lineData = getLineData();
lineChart.setData(lineData);

StackPane chartContainer = new StackPane();
new ChartZoom(lineChart, chartContainer);

Scene scene = new Scene(chartContainer, 600, 400);

stage.setScene(scene);
stage.show();
}

private ObservableList<XYChart.Series<Double, Double>> getLineData() {
Series<Double, Double> series1 = new Series<>();
Series<Double, Double> series2 = new Series<>();

for (int i = 0; i < 50; i++) {
series1.getData().add(new XYChart.Data<Double, Double>(Math.random() * 2 - 1.4, Math.random() - .7));
series2.getData().add(new XYChart.Data<Double, Double>(Math.random() * 2 - .6, Math.random() - .3));
}

ObservableList<XYChart.Series<Double, Double>> seriesList = FXCollections.observableArrayList();
seriesList.addAll(series1, series2);

return seriesList;
}

public static void main(String[] args) {
launch(args);
}
}

class ChartZoom extends Rectangle {

private StackPane chartContainer;
private LineChart lineChart;

public ChartZoom(LineChart lineChart, StackPane chartContainer) {
this.chartContainer = chartContainer;
this.lineChart = lineChart;
this.chartContainer.getChildren().add(lineChart);
this.chartContainer.getChildren().add(this);

initZoom();
setManaged(false);
setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
setUpZooming();
}

//初期状態
private void initZoom() {
NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
xAxis.setAutoRanging(true);
xAxis.setForceZeroInRange(false);
yAxis.setAutoRanging(true);
yAxis.setForceZeroInRange(false);
}

//押下、ドラッグ、離脱で座標を取得
private void setUpZooming() {
final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();
lineChart.setOnMousePressed((MouseEvent event) -> {
setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
mouseAnchor.set(new Point2D(event.getX(), event.getY()));
setWidth(0);
setHeight(0);
});

lineChart.setOnMouseDragged((MouseEvent event) -> {
double x = event.getX();
double y = event.getY();
setX(Math.min(x, mouseAnchor.get().getX()));
setY(Math.min(y, mouseAnchor.get().getY()));
setWidth(x - mouseAnchor.get().getX());
setHeight(y - mouseAnchor.get().getY());
});

lineChart.setOnMouseReleased((MouseEvent event) -> {
if ((getWidth() > 0) && (getHeight() > 0)) {
doZoom();
}else {
initZoom();
}
});
}

//拡大範囲を設定
private void doZoom() {
Point2D zoomTopLeft = new Point2D(getX(), getY());
Point2D zoomBottomRight = new Point2D(getX() + getWidth(), getY() + getHeight());
Point2D chartInScene = lineChart.localToScene(0, 0);

NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
xAxis.setAutoRanging(false);
Point2D xAxisInScene = xAxis.localToScene(0, 0);

NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
yAxis.setAutoRanging(false);

double xOffset = zoomTopLeft.getX() - xAxisInScene.getX() + chartInScene.getX();
double yOffset = zoomBottomRight.getY() - xAxisInScene.getY() + chartInScene.getY();
double xAxisScale = xAxis.getScale();
double yAxisScale = yAxis.getScale();

xAxis.setLowerBound(xAxis.getLowerBound() + xOffset / xAxisScale);
xAxis.setUpperBound(xAxis.getLowerBound() + getWidth() / xAxisScale);

yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale);
yAxis.setUpperBound(yAxis.getLowerBound() - getHeight() / yAxisScale);

setWidth(0);
setHeight(0);
}
}

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

試してみてはいないですが、ValueAxisのようにlowerBound, upperBoundはないもののsetCategoriesメソッドで表示するカテゴリー一覧を明示的に指定できそうなので・・・

  • ズーム範囲を指定する際
    getValueForDisplayで選択範囲の左右の座標位置に最も近いカテゴリーを求めておく

  • ズーム時
    表示範囲のカテゴリー範囲のリストを作成しsetCategoriesで設定する。チャートのリストと不一致の場合は自動的にsetAutoRanging(false)になるようです。

  • ズーム解除
    setCategoriesへ全データを指定しなおす

という方法が取れないでしょうか?


追記:最初「CategoryAxisを派生してValueAxisのlowerBound, upperBoundのようなプロパティーを実装してみては?」と回答しようとして、あまりに迂遠だと思い上の回答をしたのですが、期待と違う結果になるのですね・・・すみません。

ValueAxisを用いてValueに「CategoryAxisとして見せたい文字列ラベルと順序を表すComparableを両方持つ」ようなものを使い、ノードビジュアルをカスタマイズしてラベルのみを表示するように工夫したらどうだろうと思いつきました。試してないのでこれも単なるアイデアですがチャートを多用するならそうした実験もありかも知れません。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/02/14 12:32

    以下のように変更して試してみました。
    getValueForDisplayの挙動がまだよくわかっていませんが、
    軸の拡大には成功しました。
    しかし、データ点が拡大されず、ラインが消えてしまいました。

    CategoryAxisをNumberAxisにして、整数値とカテゴリを紐づけたTableViewなどを添えておくのがいい落としどころでしょうか。

    CategoryAxisである以上当初の通りに実装することは難しそうなので、
    本回答でいったん解決とします。ありがとうございます。

    public class Test extends Application {

    @Override
    public void start(Stage stage) {
    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    xAxis.setLabel("X軸");
    yAxis.setLabel("Y軸");

    //creating the chart
    final LineChart<String, Double> lineChart = new LineChart(xAxis, yAxis);
    lineChart.setLegendVisible(false);
    ObservableList<XYChart.Series<String, Double>> lineData = getLineData();
    lineChart.setData(lineData);

    StackPane chartContainer = new StackPane();
    new ChartZoom(lineChart, chartContainer);


    Scene scene = new Scene(chartContainer, 600, 400);

    stage.setScene(scene);
    stage.show();
    }

    private ObservableList<XYChart.Series<String, Double>> getLineData() {
    Series<String, Double> series1 = new Series<>();
    Series<String, Double> series2 = new Series<>();

    for (int i = 0; i < 50; i++) {
    series1.getData().add(new XYChart.Data<String, Double>(String.valueOf(i), Math.random() - .7));
    series2.getData().add(new XYChart.Data<String, Double>(String.valueOf(i), Math.random() - .3));
    }

    ObservableList<XYChart.Series<String, Double>> seriesList = FXCollections.observableArrayList();
    seriesList.addAll(series1, series2);

    return seriesList;
    }

    public static void main(String[] args) {
    launch(args);
    }
    }

    class ChartZoom extends Rectangle {

    private StackPane chartContainer;
    private LineChart lineChart;

    public ChartZoom(LineChart lineChart, StackPane chartContainer) {
    this.chartContainer = chartContainer;
    this.lineChart = lineChart;
    this.chartContainer.getChildren().add(lineChart);
    this.chartContainer.getChildren().add(this);

    initZoom();
    setManaged(false);
    setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
    setUpZooming();
    }

    //初期状態
    private void initZoom() {
    CategoryAxis xAxis = (CategoryAxis) lineChart.getXAxis();
    NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
    xAxis.setAutoRanging(true);
    //xAxis.setForceZeroInRange(false);
    yAxis.setAutoRanging(true);
    yAxis.setForceZeroInRange(false);
    }

    //押下、ドラッグ、離脱で座標を取得
    private void setUpZooming() {
    final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();
    lineChart.setOnMousePressed((MouseEvent event) -> {
    setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
    mouseAnchor.set(new Point2D(event.getX(), event.getY()));
    setWidth(0);
    setHeight(0);
    });

    lineChart.setOnMouseDragged((MouseEvent event) -> {
    double x = event.getX();
    double y = event.getY();
    setX(Math.min(x, mouseAnchor.get().getX()));
    setY(Math.min(y, mouseAnchor.get().getY()));
    setWidth(x - mouseAnchor.get().getX());
    setHeight(y - mouseAnchor.get().getY());
    });

    lineChart.setOnMouseReleased((MouseEvent event) -> {
    if ((getWidth() > 0) && (getHeight() > 0)) {
    doZoom();
    }else {
    initZoom();
    }
    });
    }

    //拡大範囲を設定
    private void doZoom() {
    Point2D zoomTopLeft = new Point2D(getX(), getY());
    Point2D zoomBottomRight = new Point2D(getX() + getWidth(), getY() + getHeight());
    Point2D chartInScene = lineChart.localToScene(0, 0);

    CategoryAxis xAxis = (CategoryAxis) lineChart.getXAxis();
    xAxis.setAutoRanging(false);
    Point2D xAxisInScene = xAxis.localToScene(0, 0);

    String x = xAxis.getValueForDisplay(getX());
    ObservableList<String> value = FXCollections.observableArrayList();
    value.add(x);

    NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
    yAxis.setAutoRanging(false);

    double xOffset = zoomTopLeft.getX() - xAxisInScene.getX() + chartInScene.getX();
    double yOffset = zoomBottomRight.getY() - xAxisInScene.getY() + chartInScene.getY();
    //double xAxisScale = xAxis.getScale();
    double yAxisScale = yAxis.getScale();

    // xAxis.setLowerBound(xAxis.getLowerBound() + xOffset / xAxisScale);
    // xAxis.setUpperBound(xAxis.getLowerBound() + getWidth() / xAxisScale);
    xAxis.setCategories(value);

    yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale);
    yAxis.setUpperBound(yAxis.getLowerBound() - getHeight() / yAxisScale);

    setWidth(0);
    setHeight(0);
    }
    }

    キャンセル

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

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

関連した質問

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

  • JavaFX

    397questions

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