JavaFX のラインチャートをズームするサンプルを以前紹介しましたが、ズームの具合がよくありませんでした。確かにズームはできますが、マウスで指定した矩形の領域が正確に拡大されていません。どうやら座標計算が正確では無いようです。手軽にチャートのズームができる方法で気に入っていたので、じっくり調べて修正しました。いや、どうしてもズーム機能を使う目的があったため、なんとか実用に耐えうるレベルまで修正する必要があった、というのが現実です。
動作環境は次の通りです。
- OS: Fedora 23 x86_64
- jdk1.8.0_77-1.8.0_77-fcs.x86_64 (Oracle)
- IDE: NetBeans IDE 8.1
まず最初に実行例を示します。
ソースは、もともと参考にした GitHub Gist のサイト [1] を fork して、修正を加えましたので、それを埋め込みました [2]。最初の LineChartZoomable.java が、LineChart クラスを継承して、ズーム機能を加えたクラス、その下の LineChartZoomableTest.java は、LineChartZoomable クラスを利用したサンプルです。なお、このクラスは、StackPane で最初の配置を行う必要があります。
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
package linechartzoomable; | |
import javafx.beans.property.ObjectProperty; | |
import javafx.beans.property.SimpleObjectProperty; | |
import javafx.geometry.Point2D; | |
import javafx.scene.chart.Axis; | |
import javafx.scene.chart.LineChart; | |
import javafx.scene.chart.NumberAxis; | |
import javafx.scene.input.MouseEvent; | |
import javafx.scene.layout.StackPane; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.Rectangle; | |
public class LineChartZoomable<X, Y> extends LineChart<X, Y> { | |
private final Rectangle rect; | |
private boolean isFirstZoom = true; | |
private boolean xAxisAutoRanging, yAxisAutoRanging; | |
private double xAxisLowerBound, xAxisUpperBound, yAxisLowerBound, yAxisUpperBound; | |
public LineChartZoomable(Axis<X> xAxis, Axis<Y> yAxis) { | |
super(xAxis, yAxis); | |
rect = new Rectangle(); | |
rect.setManaged(false); | |
rect.setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5)); | |
setUpZooming(); | |
} | |
private void setUpZooming() { | |
ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>(); | |
setOnMousePressed((MouseEvent event) -> { | |
double x = event.getX(); | |
double y = event.getY(); | |
mouseAnchor.set(new Point2D(x, y)); | |
rect.setWidth(0); | |
rect.setHeight(0); | |
StackPane pane = (StackPane) getParent(); | |
pane.getChildren().add(rect); | |
if (isFirstZoom) { | |
NumberAxis xAxis = (NumberAxis) getXAxis(); | |
xAxisAutoRanging = xAxis.isAutoRanging(); | |
xAxisLowerBound = xAxis.getLowerBound(); | |
xAxisUpperBound = xAxis.getUpperBound(); | |
NumberAxis yAxis = (NumberAxis) getYAxis(); | |
yAxisAutoRanging = yAxis.isAutoRanging(); | |
yAxisLowerBound = yAxis.getLowerBound(); | |
yAxisUpperBound = yAxis.getUpperBound(); | |
isFirstZoom = false; | |
} | |
}); | |
setOnMouseDragged((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(x - mouseAnchor.get().getX()); | |
rect.setHeight(y - mouseAnchor.get().getY()); | |
}); | |
setOnMouseReleased((MouseEvent event) -> { | |
StackPane pane = (StackPane) getParent(); | |
pane.getChildren().remove(rect); | |
if ((rect.getWidth() > 0) && (rect.getHeight() > 0)) { | |
doZoom(); | |
} else { | |
releaseZoom(); | |
} | |
}); | |
} | |
private void doZoom() { | |
Point2D zoomTopLeft = new Point2D(rect.getX(), rect.getY()); | |
Point2D zoomBottomRight = new Point2D(rect.getX() + rect.getWidth(), rect.getY() + rect.getHeight()); | |
Point2D chartInScene = this.localToScene(0, 0); | |
NumberAxis xAxis = (NumberAxis) getXAxis(); | |
xAxis.setAutoRanging(false); | |
Point2D xAxisInScene = xAxis.localToScene(0, 0); | |
NumberAxis yAxis = (NumberAxis) 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() + rect.getWidth() / xAxisScale); | |
yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale); | |
yAxis.setUpperBound(yAxis.getLowerBound() - rect.getHeight() / yAxisScale); | |
rect.setWidth(0); | |
rect.setHeight(0); | |
} | |
private void releaseZoom() { | |
setWidth(0); | |
setHeight(0); | |
NumberAxis xAxis = (NumberAxis) getXAxis(); | |
xAxis.setAutoRanging(xAxisAutoRanging); | |
xAxis.setLowerBound(xAxisLowerBound); | |
xAxis.setUpperBound(xAxisUpperBound); | |
NumberAxis yAxis = (NumberAxis) getYAxis(); | |
yAxis.setAutoRanging(yAxisAutoRanging); | |
yAxis.setLowerBound(yAxisLowerBound); | |
yAxis.setUpperBound(yAxisUpperBound); | |
isFirstZoom = true; | |
} | |
} |
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
package linechartzoomable; | |
import javafx.application.Application; | |
import javafx.event.ActionEvent; | |
import javafx.scene.Scene; | |
import javafx.scene.chart.NumberAxis; | |
import javafx.scene.chart.XYChart; | |
import javafx.scene.control.Menu; | |
import javafx.scene.control.MenuBar; | |
import javafx.scene.control.MenuItem; | |
import javafx.scene.control.Tooltip; | |
import javafx.scene.layout.BorderPane; | |
import javafx.scene.layout.StackPane; | |
import javafx.stage.Stage; | |
import static javafx.application.Application.launch; | |
public class LineChartZoomableTest extends Application { | |
private static final double[][] DATA = new double[][]{ | |
{1, 17.795}, {2, 18.060}, {3, 18.755}, {4, 19.140}, {5, 19.250}, | |
{6, 18.855}, {7, 18.710}, {8, 18.780}, {9, 19.015}, {10, 19.490}, | |
{11, 19.545}, {12, 19.50}, {13, 19.235}, {14, 19.390}, {15, 19.895}, | |
{16, 20.145}, {17, 20.270}, {18, 19.980}, {19, 19.825}, {20, 19.805}, | |
{21, 19.720}, {22, 19.710}, {23, 19.710}, {24, 19.565}, {25, 19.690}, | |
{26, 19.925}, {27, 20.050}, {28, 19.860}, {29, 20.025}, {30, 20.080}, | |
{31, 20.085}, {32, 20.460}, {33, 20.340}, {34, 20.185}, {35, 20.245}, | |
{36, 20.445}, {37, 20.230}, {38, 20.060}, {39, 20.175}, {40, 20.040}, | |
{41, 19.970}, {42, 20.130}, {43, 20.180}, {44, 20.355}, {45, 19.945}, | |
{46, 19.910}, {47, 19.885}, {48, 19.845}, {49, 19.750}, {50, 19.435}, | |
{51, 19.365}, {52, 19.580}, {53, 19.970}, {54, 19.965}, {55, 19.785}, | |
{56, 19.820}, {57, 20.240}, {58, 20.095}, {59, 22.050}, {60, 22.095}, | |
{61, 22.215}, {62, 22.365}, {63, 21.805}, {64, 21.565}, {65, 21.965}, | |
{66, 22.380} | |
}; | |
@Override | |
public void start(Stage stage) { | |
BorderPane pane = new BorderPane(); | |
MenuBar mbar = createButtonBar(); | |
pane.setTop(mbar); | |
StackPane container = createChart(); | |
pane.setCenter(container); | |
Scene scene = new Scene(pane); | |
stage.setScene(scene); | |
stage.show(); | |
} | |
private MenuBar createButtonBar() { | |
MenuBar menuBar = new MenuBar(); | |
Menu menuFile = new Menu("File"); | |
menuBar.getMenus().addAll(menuFile); | |
MenuItem itemExit = new MenuItem("Exit"); | |
itemExit.setOnAction((ActionEvent t) -> { | |
System.exit(0); | |
}); | |
menuFile.getItems().addAll(itemExit); | |
return menuBar; | |
} | |
private StackPane createChart() { | |
XYChart.Series<Number, Number> series = new XYChart.Series<>(); | |
series.setName("data"); | |
for (double[] data : DATA) { | |
series.getData().add(new XYChart.Data<>(data[0], data[1])); | |
} | |
NumberAxis xAxis = new NumberAxis(); | |
xAxis.setLabel("DATE"); | |
NumberAxis yAxis = new NumberAxis(); | |
yAxis.setLabel("VALUE"); | |
yAxis.setForceZeroInRange(false); | |
final LineChartZoomable<Number, Number> chart = new LineChartZoomable<>(xAxis, yAxis); | |
chart.setAnimated(false); | |
chart.setCreateSymbols(true); | |
chart.setTitle("Trend Example"); | |
chart.getData().add(series); | |
// Tooltip | |
chart.getData().stream().forEach((s) -> { | |
s.getData().stream().forEach((d) -> { | |
double x = d.getXValue().doubleValue(); | |
double y = d.getYValue().doubleValue(); | |
Tooltip tooltip = new Tooltip(String.format("(%2.0f, %6.3f)", x, y)); | |
Tooltip.install(d.getNode(), tooltip); | |
}); | |
}); | |
StackPane stackPane = new StackPane(); | |
stackPane.getChildren().add(chart); | |
return stackPane; | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
参考サイト
- Example of a LineChart that can be zoomed via mouse.
- LineChartZoomable class that can be zoomed via mouse.
にほんブログ村
0 件のコメント:
コメントを投稿