Skip to content

Instantly share code, notes, and snippets.

@bitwalk123
Forked from james-d/ZoomableLineChart.java
Last active April 6, 2016 03:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bitwalk123/88372d8913a571af4e551f4812e2d98b to your computer and use it in GitHub Desktop.
Save bitwalk123/88372d8913a571af4e551f4812e2d98b to your computer and use it in GitHub Desktop.
LineChartZoomable class that can be zoomed via mouse.
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;
}
}
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);
}
}
@bitwalk123
Copy link
Author

It is required to use StackPane to lay out the instance of LineChartZoomable class at the first time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment