JavaFX には基本的なグラフ(チャート)を作成するクラスが標準で用意されているので、ある程度使い込めば、自分の作成したいグラフを自由自在に作成できるようになります…、と言いたいところですが、それでも標準で備わっていてもらいたい機能がいくつかあります。そのひとつがズーム機能です。必要であれば自分で作ってしまうべきなのですが、世の中には同じようなことを考える人がいて、既に紹介されているサンプルがあります。
今回は、GitHub Gist で公開されている ZoomableLineChart クラスを紹介します。
実行例を下記に示しました。動作を確認した環境は次の通りです。
- OS: Fedora 23 (x86_64)
- Java: jdk1.8.0_74-1.8.0_74-fcs.x86_64 (Oracle)
- IDE: NetBeans IDE 8.1
このサンプルでは、欲しかったズーム機能が実現されています。願わくば、ボタンをクリックしなくとも、マウスの操作だけでスームや解除をしたいところです。また、クラスを汎用目的に使えるようにしたいのですが、必要であれば自分でやるべきことだと思うので、ソースの内容をよく咀嚼して、自分が使いやすい汎用クラスへ改良しようと思います。
[1] のソースを下記に埋め込みました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Collections; | |
import java.util.Random; | |
import javafx.application.Application; | |
import javafx.beans.binding.BooleanBinding; | |
import javafx.beans.property.ObjectProperty; | |
import javafx.beans.property.SimpleObjectProperty; | |
import javafx.collections.FXCollections; | |
import javafx.collections.ObservableList; | |
import javafx.event.ActionEvent; | |
import javafx.event.EventHandler; | |
import javafx.geometry.Insets; | |
import javafx.geometry.Point2D; | |
import javafx.geometry.Pos; | |
import javafx.scene.Node; | |
import javafx.scene.Scene; | |
import javafx.scene.chart.Axis; | |
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.control.Button; | |
import javafx.scene.input.MouseEvent; | |
import javafx.scene.layout.BorderPane; | |
import javafx.scene.layout.HBox; | |
import javafx.scene.layout.StackPane; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.Rectangle; | |
import javafx.stage.Stage; | |
public class ZoomableLineChart extends Application { | |
private static final int NUM_DATA_POINTS = 1000 ; | |
@Override | |
public void start(Stage primaryStage) { | |
final LineChart<Number, Number> chart = createChart(); | |
final StackPane chartContainer = new StackPane(); | |
chartContainer.getChildren().add(chart); | |
final Rectangle zoomRect = new Rectangle(); | |
zoomRect.setManaged(false); | |
zoomRect.setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5)); | |
chartContainer.getChildren().add(zoomRect); | |
setUpZooming(zoomRect, chart); | |
final HBox controls = new HBox(10); | |
controls.setPadding(new Insets(10)); | |
controls.setAlignment(Pos.CENTER); | |
final Button zoomButton = new Button("Zoom"); | |
final Button resetButton = new Button("Reset"); | |
zoomButton.setOnAction(new EventHandler<ActionEvent>() { | |
@Override | |
public void handle(ActionEvent event) { | |
doZoom(zoomRect, chart); | |
} | |
}); | |
resetButton.setOnAction(new EventHandler<ActionEvent>() { | |
@Override | |
public void handle(ActionEvent event) { | |
final NumberAxis xAxis = (NumberAxis)chart.getXAxis(); | |
xAxis.setLowerBound(0); | |
xAxis.setUpperBound(1000); | |
final NumberAxis yAxis = (NumberAxis)chart.getYAxis(); | |
yAxis.setLowerBound(0); | |
yAxis.setUpperBound(1000); | |
zoomRect.setWidth(0); | |
zoomRect.setHeight(0); | |
} | |
}); | |
final BooleanBinding disableControls = | |
zoomRect.widthProperty().lessThan(5) | |
.or(zoomRect.heightProperty().lessThan(5)); | |
zoomButton.disableProperty().bind(disableControls); | |
controls.getChildren().addAll(zoomButton, resetButton); | |
final BorderPane root = new BorderPane(); | |
root.setCenter(chartContainer); | |
root.setBottom(controls); | |
final Scene scene = new Scene(root, 600, 400); | |
primaryStage.setScene(scene); | |
primaryStage.show(); | |
} | |
private LineChart<Number, Number> createChart() { | |
final NumberAxis xAxis = createAxis(); | |
final NumberAxis yAxis = createAxis(); | |
final 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<Number, Number>(i, rng.nextInt(1000)); | |
series.getData().add(dataPoint); | |
} | |
return FXCollections.observableArrayList(Collections.singleton(series)); | |
} | |
private void setUpZooming(final Rectangle rect, final Node zoomingNode) { | |
final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>(); | |
zoomingNode.setOnMousePressed(new EventHandler<MouseEvent>() { | |
@Override | |
public void handle(MouseEvent event) { | |
mouseAnchor.set(new Point2D(event.getX(), event.getY())); | |
rect.setWidth(0); | |
rect.setHeight(0); | |
} | |
}); | |
zoomingNode.setOnMouseDragged(new EventHandler<MouseEvent>() { | |
@Override | |
public void handle(MouseEvent event) { | |
double x = event.getX(); | |
double y = event.getY(); | |
rect.setX(Math.min(x, mouseAnchor.get().getX())); | |
rect.setY(Math.min(y, mouseAnchor.get().getY())); | |
rect.setWidth(Math.abs(x - mouseAnchor.get().getX())); | |
rect.setHeight(Math.abs(y - mouseAnchor.get().getY())); | |
} | |
}); | |
} | |
private void doZoom(Rectangle zoomRect, LineChart<Number, Number> chart) { | |
Point2D zoomTopLeft = new Point2D(zoomRect.getX(), zoomRect.getY()); | |
Point2D zoomBottomRight = new Point2D(zoomRect.getX() + zoomRect.getWidth(), zoomRect.getY() + zoomRect.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() + zoomRect.getWidth() / xAxisScale); | |
yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale); | |
yAxis.setUpperBound(yAxis.getLowerBound() - zoomRect.getHeight() / yAxisScale); | |
System.out.println(yAxis.getLowerBound() + " " + yAxis.getUpperBound()); | |
zoomRect.setWidth(0); | |
zoomRect.setHeight(0); | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
0 件のコメント:
コメントを投稿