2016-03-21

JavaFX: LineChart を使いこなそう (6) - ズーム(改)

JavaFX のラインチャートをズームするサンプルを、以前の記事 [1] で紹介しましたが、コードを整理し、ズームおよび解除をプッシュボタン無しにできるように変更してみました。すなわち、マウス左ボタンを押しながらチャート上で左上から右下までドラッグしてマウスの左ボタンを離したときにズームを実行し、それ以外のドラッグをした場合、例えば、右上から左下まで左ボタンを押しながらドラッグするとズームが解除されるようにしました。

動作環境は次の通りです。

  • OS: Fedora 23 x86_64
  • jdk1.8.0_74-1.8.0_74-fcs.x86_64 (Oracle)
  • IDE: NetBeans IDE 8.1

まずは、ズームの機能を担う ZoomableLayer クラスです。このクラスのインスタンスを生成して、LineChart のインスタンスの上に、StackPane クラスで重ねて使用します。

リスト:ZoomableLayer.java 
package zoomablelayer;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class ZoomableLayer extends Rectangle {

    LineChart chart;
    double xLower, xUpper, yLower, yUpper;

    public ZoomableLayer(LineChart chart) {
        this.chart = chart;
        initZoom();
        setManaged(false);
        setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
        setUpZooming();
    }

    private void initZoom() {
        NumberAxis xAxis = (NumberAxis) chart.getXAxis();
        xLower = xAxis.getLowerBound();
        xUpper = xAxis.getUpperBound();
        NumberAxis yAxis = (NumberAxis) chart.getYAxis();
        yLower = yAxis.getLowerBound();
        yUpper = yAxis.getUpperBound();
    }

    private void setUpZooming() {
        final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();

        chart.setOnMousePressed((MouseEvent event) -> {
            mouseAnchor.set(new Point2D(event.getX(), event.getY()));
            setWidth(0);
            setHeight(0);
        });

        chart.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());
        });

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

    private void doZoom() {
        Point2D zoomTopLeft = new Point2D(getX(), getY());
        Point2D zoomBottomRight = new Point2D(getX() + getWidth(), getY() + getHeight());

        final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
        Point2D yAxisInScene = yAxis.localToScene(0, 0);
        final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
        Point2D xAxisInScene = xAxis.localToScene(0, 0);

        double xOffset = zoomTopLeft.getX() - yAxisInScene.getX();
        double yOffset = zoomBottomRight.getY() - xAxisInScene.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);

        System.out.println(yAxis.getLowerBound() + " " + yAxis.getUpperBound());

        setWidth(0);
        setHeight(0);
    }

    private void releaseZoom() {
        NumberAxis xAxis = (NumberAxis) chart.getXAxis();
        xAxis.setLowerBound(xLower);
        xAxis.setUpperBound(xUpper);
        NumberAxis yAxis = (NumberAxis) chart.getYAxis();
        yAxis.setLowerBound(yLower);
        yAxis.setUpperBound(yUpper);

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

LineChart と ZoomableLayer クラスのインスタンスを StackPane で重ねて表示するサンプルです。

リスト:SampleLineChart.java 
package zoomablelayer;

import java.util.Collections;
import java.util.Random;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class SampleLineChart extends Application {

    private static final int NUM_DATA_POINTS = 1000;

    @Override
    public void start(Stage primaryStage) {
        StackPane chartContainer = new StackPane();

        LineChart<Number, Number> chart = createChart();
        chartContainer.getChildren().add(chart);

        ZoomableLayer zoomRect = new ZoomableLayer(chart);
        chartContainer.getChildren().add(zoomRect);

        Scene scene = new Scene(chartContainer, 600, 400);
        primaryStage.setTitle(getClass().getSimpleName());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private LineChart<Number, Number> createChart() {
        final NumberAxis xAxis = createAxis();
        final NumberAxis yAxis = createAxis();
        LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
        chart.setAnimated(false);
        chart.setCreateSymbols(false);
        chart.setData(generateChartData());
        return chart;
    }

    private NumberAxis createAxis() {
        final NumberAxis xAxis = new NumberAxis();
        xAxis.setAutoRanging(false);
        xAxis.setLowerBound(0);
        xAxis.setUpperBound(1000);
        return xAxis;
    }

    private ObservableList<Series<Number, Number>> generateChartData() {
        final Series<Number, Number> series = new Series<>();
        series.setName("Data");
        final Random rng = new Random();
        for (int i = 0; i < NUM_DATA_POINTS; i++) {
            Data<Number, Number> dataPoint = new Data<>(i, rng.nextInt(1000));
            series.getData().add(dataPoint);
        }
        return FXCollections.observableArrayList(Collections.singleton(series));
    }

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

実行例を以下に示します。

LineChart を拡張したクラスにズーム機能を入れ込んだ方が簡単で良いのですが、現状の方法をそのまま使うのではなく、もっとスマートにズーム機能を追加できる方法がないか検討中です。

参考サイト

  1. bitWalk's: JavaFX: LineChart を使いこなそう (4) - ズーム
  2. Example of a LineChart that can be zoomed via mouse. · GitHub

 

ブログランキング・にほんブログ村へ
にほんブログ村

0 件のコメント: