我从这个答案更新了图形解决方案以添加缩放功能:
为了添加交互式缩放,我为滚动事件添加了一个处理程序。在滚动事件处理程序中,我计算坐标轴和绘图坐标的低值和高值的新值,然后将它们应用于坐标轴和绘图。
我使用在鼠标滚轮或触摸板或触摸屏滚动手势上工作的scroll event handler。但您也可以(或改为)使用zoom event handler,它利用触摸表面上的缩放(捏合)手势。
当检测到滚动时,我只是放大或缩小固定量(当前缩放系数的 10%),直到最小或最大缩放值。更复杂的解决方案可以查询滚动或缩放事件的增量值,以根据滚动事件的速度实现惯性滚动和更大或更少的滚动。
为了实现缩放,我重新创建了缩放的节点,而不是更新现有节点的属性,这可能不是那么有效。但是,在我的简单测试用例中,性能似乎还不错,所以我认为不值得付出额外的努力来优化。
这只是这个问题的众多潜在解决方案之一(我不会在这里讨论其他潜在的解决方案)。此答案中提供的特定解决方案似乎很适合我。
另外,请注意,此解决方案不使用画布,它基于场景图。我建议为此任务使用场景图,但如果您愿意,也可以使用画布。对于画布解决方案,该解决方案可能与此处介绍的解决方案大不相同(我不提供任何关于如何创建基于画布的解决方案的建议)。
用于处理缩放的事件处理程序
此处理程序附加到包含图形节点子节点的父窗格。
private class ZoomHandler implements EventHandler<ScrollEvent> {
private static final double MAX_ZOOM = 2;
private static final double MIN_ZOOM = 0.5;
private double zoomFactor = 1;
@Override
public void handle(ScrollEvent event) {
if (event.getDeltaY() == 0) {
return;
} else if (event.getDeltaY() < 0) {
zoomFactor = Math.max(MIN_ZOOM, zoomFactor * 0.9);
} else if (event.getDeltaY() > 0) {
zoomFactor = Math.min(MAX_ZOOM, zoomFactor * 1.1);
}
Plot plot = plotChart(zoomFactor);
Pane parent = (Pane) event.getSource();
parent.getChildren().setAll(plot);
}
}
缩放图表图像示例
一直放大
默认缩放级别
一直放大
完整的示例解决方案代码
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import java.util.function.Function;
public class ZoomableCartesianPlot extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(final Stage stage) {
Plot plot = plotChart(1);
StackPane layout = new StackPane(
plot
);
layout.setPadding(new Insets(20));
layout.setStyle("-fx-background-color: rgb(35, 39, 50);");
layout.setOnScroll(new ZoomHandler());
stage.setTitle("y = \u00BC(x+4)(x+1)(x-2)");
stage.setScene(new Scene(layout, Color.rgb(35, 39, 50)));
stage.show();
}
private Plot plotChart(double zoomFactor) {
Axes axes = new Axes(
400, 300,
-8 * zoomFactor, 8 * zoomFactor, 1,
-6 * zoomFactor, 6 * zoomFactor, 1
);
Plot plot = new Plot(
x -> .25 * (x + 4) * (x + 1) * (x - 2),
-8 * zoomFactor, 8 * zoomFactor, 0.1,
axes
);
return plot;
}
class Axes extends Pane {
private NumberAxis xAxis;
private NumberAxis yAxis;
public Axes(
int width, int height,
double xLow, double xHi, double xTickUnit,
double yLow, double yHi, double yTickUnit
) {
setMinSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);
setPrefSize(width, height);
setMaxSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);
xAxis = new NumberAxis(xLow, xHi, xTickUnit);
xAxis.setSide(Side.BOTTOM);
xAxis.setMinorTickVisible(false);
xAxis.setPrefWidth(width);
xAxis.setLayoutY(height / 2);
yAxis = new NumberAxis(yLow, yHi, yTickUnit);
yAxis.setSide(Side.LEFT);
yAxis.setMinorTickVisible(false);
yAxis.setPrefHeight(height);
yAxis.layoutXProperty().bind(
Bindings.subtract(
(width / 2) + 1,
yAxis.widthProperty()
)
);
getChildren().setAll(xAxis, yAxis);
}
public NumberAxis getXAxis() {
return xAxis;
}
public NumberAxis getYAxis() {
return yAxis;
}
}
class Plot extends Pane {
public Plot(
Function<Double, Double> f,
double xMin, double xMax, double xInc,
Axes axes
) {
Path path = new Path();
path.setStroke(Color.ORANGE.deriveColor(0, 1, 1, 0.6));
path.setStrokeWidth(2);
path.setClip(
new Rectangle(
0, 0,
axes.getPrefWidth(),
axes.getPrefHeight()
)
);
double x = xMin;
double y = f.apply(x);
path.getElements().add(
new MoveTo(
mapX(x, axes), mapY(y, axes)
)
);
x += xInc;
while (x < xMax) {
y = f.apply(x);
path.getElements().add(
new LineTo(
mapX(x, axes), mapY(y, axes)
)
);
x += xInc;
}
setMinSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);
setPrefSize(axes.getPrefWidth(), axes.getPrefHeight());
setMaxSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);
getChildren().setAll(axes, path);
}
private double mapX(double x, Axes axes) {
double tx = axes.getPrefWidth() / 2;
double sx = axes.getPrefWidth() /
(axes.getXAxis().getUpperBound() -
axes.getXAxis().getLowerBound());
return x * sx + tx;
}
private double mapY(double y, Axes axes) {
double ty = axes.getPrefHeight() / 2;
double sy = axes.getPrefHeight() /
(axes.getYAxis().getUpperBound() -
axes.getYAxis().getLowerBound());
return -y * sy + ty;
}
}
private class ZoomHandler implements EventHandler<ScrollEvent> {
private static final double MAX_ZOOM = 2;
private static final double MIN_ZOOM = 0.5;
private double zoomFactor = 1;
@Override
public void handle(ScrollEvent event) {
if (event.getDeltaY() == 0) {
return;
} else if (event.getDeltaY() < 0) {
zoomFactor = Math.max(MIN_ZOOM, zoomFactor * 0.9);
} else if (event.getDeltaY() > 0) {
zoomFactor = Math.min(MAX_ZOOM, zoomFactor * 1.1);
}
Plot plot = plotChart(zoomFactor);
Pane parent = (Pane) event.getSource();
parent.getChildren().setAll(plot);
}
}
}