我正在做这个绘图项目,但我遇到了这个问题:每次放大或缩小时,我的 x 轴和 y 轴都不合适。知道如何解决这个问题吗?
这是我的代码:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class GraphingCal extends Application {
private static final int CANVAS_WIDTH = 900;
private static final int CANVAS_HEIGHT = 700;
private static final double INITIAL_GRID_SIZE = 20;
private static final double ZOOM_FACTOR = 1.1;
private static final double MIN_GRID_SIZE = INITIAL_GRID_SIZE * 0.6;
private static final double MAX_GRID_SIZE = INITIAL_GRID_SIZE / 0.6;
private double gridSize = INITIAL_GRID_SIZE;
private double xOffset = 0;
private double yOffset = 0;
private double prevX;
private double prevY;
@Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene scene = new Scene(root, CANVAS_WIDTH, CANVAS_HEIGHT);
Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
drawGrid(gc);
canvas.setOnMousePressed(e -> {
prevX = e.getX();
prevY = e.getY();
});
canvas.setOnMouseDragged(e -> {
double dx = e.getX() - prevX;
double dy = e.getY() - prevY;
xOffset += dx;
yOffset += dy;
prevX = e.getX();
prevY = e.getY();
drawGrid(gc);
});
canvas.setOnScroll((event) -> {
double mouseX = event.getX();
double mouseY = event.getY();
double zoomFactor = event.getDeltaY() > 0 ? ZOOM_FACTOR : 1 / ZOOM_FACTOR;
double newGridSize = gridSize * zoomFactor;
if (newGridSize >= MIN_GRID_SIZE && newGridSize <= MAX_GRID_SIZE) {
// Calculate the change in grid size
double deltaGridSize = newGridSize - gridSize;
xOffset -= mouseX * deltaGridSize / gridSize;
yOffset -= mouseY * deltaGridSize / gridSize;
gridSize = newGridSize;
} else {
gridSize = INITIAL_GRID_SIZE;
}
drawGrid(gc);
});
root.getChildren().add(canvas);
primaryStage.setTitle("Graphing Calculator");
primaryStage.setScene(scene);
primaryStage.show();
}
private void drawGrid(GraphicsContext gc) {
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Calculate zoomed center positions
double centerX = CANVAS_WIDTH / 3.0 + xOffset;
double centerY = CANVAS_HEIGHT / 7.0 + yOffset;
// Calculate the distance between each highlighted line
double highlightedLineDistanceX = gridSize * 5;
double highlightedLineDistanceY = gridSize * 5;
// Draw main grid lines
gc.setStroke(Color.LIGHTGRAY);
for (double x = (xOffset % gridSize + CANVAS_WIDTH) % gridSize; x < CANVAS_WIDTH; x += gridSize) {
gc.strokeLine(x, 0, x, CANVAS_HEIGHT);
}
for (double y = (yOffset % gridSize + CANVAS_HEIGHT) % gridSize; y < CANVAS_HEIGHT; y += gridSize) {
gc.strokeLine(0, y, CANVAS_WIDTH, y);
}
// Draw highlighted lines aligned with x-axis scale
gc.setStroke(Color.rgb(128, 128, 128));
for (double x = (xOffset % highlightedLineDistanceX + CANVAS_WIDTH) % highlightedLineDistanceX; x < CANVAS_WIDTH; x += highlightedLineDistanceX) {
gc.strokeLine(x, 0, x, CANVAS_HEIGHT);
}
// Draw highlighted lines aligned with y-axis scale
for (double y = (yOffset % highlightedLineDistanceY + CANVAS_HEIGHT) % highlightedLineDistanceY; y < CANVAS_HEIGHT; y += highlightedLineDistanceY) {
gc.strokeLine(0, y, CANVAS_WIDTH, y);
}
// Draw x-axis and y-axis
gc.setStroke(Color.BLACK);
gc.strokeLine(0, centerY, CANVAS_WIDTH, centerY); // x-axis
gc.strokeLine(centerX, 0, centerX, CANVAS_HEIGHT); // y-axis
// Draw axis labels
gc.setFill(Color.BLACK);
gc.fillText("0", centerX + 5, centerY + 5); // Origin label
gc.fillText("x", CANVAS_WIDTH - 10, centerY + 15); // x-axis label
gc.fillText("y", centerX + 5, 15); // y-axis label
// Calculate start and end scale positions based on yOffset and gridSize
int startScaleY = (int) Math.ceil((-yOffset - CANVAS_HEIGHT / 7.0) / (highlightedLineDistanceY));
int endScaleY = (int) Math.ceil((CANVAS_HEIGHT - yOffset - CANVAS_HEIGHT / 7.0) / (highlightedLineDistanceY));
// Draw scale for y-axis
for (int i = startScaleY; i <= endScaleY; i++) {
double y = CANVAS_HEIGHT / 7.0 + highlightedLineDistanceY * i + yOffset;
if (i != 0) {
gc.strokeLine(centerX + 5, y, centerX - 5, y);
gc.fillText(String.valueOf(i * -5), centerX - 30, y + 3);
}
}
// Calculate start and end scale positions based on xOffset and gridSize
int startScaleX = (int) Math.ceil((-xOffset - CANVAS_WIDTH / 3.0) / (highlightedLineDistanceX));
int endScaleX = (int) Math.ceil((CANVAS_WIDTH - xOffset - CANVAS_WIDTH / 3.0) / (highlightedLineDistanceX));
// Draw scale for x-axis
for (int i = startScaleX; i <= endScaleX; i++) {
double x = CANVAS_WIDTH / 3.0 + highlightedLineDistanceX * i + xOffset;
if (i != 0) {
gc.strokeLine(x, centerY - 5, x, centerY + 5);
gc.fillText(String.valueOf(i * 5), x - 3, centerY + 20);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
我尝试根据当前缩放级别和偏移来计算它们的位置,以适应放大和缩小时发生的间隙。
(对我来说)理解你想要做的核心逻辑有点困难。 所以我尝试从头开始做同样的事情,坚持你遵循的相同原则。
我所依赖的关键逻辑是:
一旦掌握了以上因素,就很容易画出你喜欢的画布了。
请查看以下演示代码以供参考。我添加了一些条件功能,只是为了展示一旦具备上述三个因素,绘图的灵活性。令人信服的部分是我们不会在画布边界之外绘制任何内容;)
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class GraphingCalModified extends Application {
private static final int CANVAS_WIDTH = 900;
private static final int CANVAS_HEIGHT = 700;
private static final int INITIAL_GRID_SIZE = 20;
private static final double MIN_GRID_SIZE = INITIAL_GRID_SIZE * 0.6;
private static final double MAX_GRID_SIZE = INITIAL_GRID_SIZE / 0.6;
private static final double SCALE_FACTOR = .1;
private int gridSize = INITIAL_GRID_SIZE;
private double prevX;
private double prevY;
private DoubleProperty scale = new SimpleDoubleProperty(1);
private DoubleProperty xOffset = new SimpleDoubleProperty();
private DoubleProperty yOffset = new SimpleDoubleProperty();
private int mouseX, mouseY;
private boolean showMousePos, showOverflowX, showOverflowY;
int highlightedLineDistanceX = gridSize * 5;
int highlightedLineDistanceY = gridSize * 5;
@Override
public void start(Stage primaryStage) {
Label scaleLabel = new Label((scale.intValue() * 100) + "%");
scaleLabel.setMouseTransparent(true);
scaleLabel.setStyle("-fx-background-color:grey;-fx-opacity:.8;-fx-text-fill:white;-fx-background-radius:10px;-fx-padding:5px;");
scale.addListener((obs, old, val) -> scaleLabel.setText((int) (val.doubleValue() * 100) + "%"));
Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
final CheckBox showMousePosition = new CheckBox("Show Mouse Position");
showMousePosition.selectedProperty().addListener((obs, old, val) -> {
showMousePos = val;
});
showMousePosition.setSelected(true);
final CheckBox overflowX = new CheckBox("Show Overflow X axis");
overflowX.selectedProperty().addListener((obs, old, val) -> {
showOverflowX = val;
drawGrid(gc);
});
overflowX.setSelected(true);
final CheckBox overflowY = new CheckBox("Show Overflow Y axis");
overflowY.selectedProperty().addListener((obs, old, val) -> {
showOverflowY = val;
drawGrid(gc);
});
overflowY.setSelected(true);
HBox controls = new HBox(10, scaleLabel, showMousePosition, overflowX, overflowY);
controls.setStyle("-fx-font-size:20px;");
controls.setAlignment(Pos.CENTER_LEFT);
StackPane canvasBox = new StackPane(canvas);
canvasBox.setStyle("-fx-border-width:1px;-fx-border-color:lightgray;");
VBox box = new VBox(10, controls, new Group(canvasBox));
box.setAlignment(Pos.CENTER);
box.setPadding(new Insets(10));
drawGrid(gc);
canvas.setOnMousePressed(e -> {
prevX = e.getX();
prevY = e.getY();
});
canvas.setOnMouseDragged(e -> {
mouseX = (int) e.getX();
mouseY = (int) e.getY();
double dx = e.getX() - prevX;
double dy = e.getY() - prevY;
xOffset.set(xOffset.get() + (dx / scale.get()));
yOffset.set(yOffset.get() + (dy / scale.get()));
prevX = e.getX();
prevY = e.getY();
drawGrid(gc);
});
canvas.setOnScroll((event) -> {
mouseX = (int) event.getX();
mouseY = (int) event.getY();
Point before = gridPointAtMouse();
// Calculate the scale and the grid size
double tempScale = event.getDeltaY() > 0 ? scale.get() + SCALE_FACTOR : scale.get() - SCALE_FACTOR;
double newGridSize = INITIAL_GRID_SIZE * tempScale;
if (newGridSize >= MIN_GRID_SIZE && newGridSize <= MAX_GRID_SIZE) {
scale.set(tempScale);
gridSize = (int) newGridSize;
} else {
gridSize = INITIAL_GRID_SIZE;
scale.set(1);
}
// Calculate the distance between each highlighted line
highlightedLineDistanceX = gridSize * 5;
highlightedLineDistanceY = gridSize * 5;
// Calculate the offset that need to be moved to ensure the mouse stays at same grid point.
Point after = gridPointAtMouse();
xOffset.set(xOffset.get() + (after.x - before.x) / scale.get());
yOffset.set(yOffset.get() + (after.y - before.y) / scale.get());
drawGrid(gc);
});
canvas.setOnMouseMoved(event -> {
mouseX = (int) event.getX();
mouseY = (int) event.getY();
drawGrid(gc);
});
canvas.setOnMouseExited(event -> {
mouseX = -1;
mouseY = -1;
drawGrid(gc);
});
primaryStage.setTitle("Graphing Calculator");
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
private void drawGrid(GraphicsContext gc) {
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Calculate zoomed center position
final Point zoomedCenter = zoomedCenter();
int centerX = zoomedCenter.x;
int centerY = zoomedCenter.y;
boolean xVisible = true;
boolean yVisible = true;
// Calculate the first visible vertical line x offset
int nx = Math.abs(centerX / gridSize);
int startX;
if (centerX < 0) {
startX = centerX + (nx * gridSize);
yVisible = false;
} else {
startX = centerX - (nx * gridSize);
if (centerX > CANVAS_WIDTH) {
yVisible = false;
}
}
// Calculate the first visible horizontal line x offset
int ny = Math.abs(centerY / gridSize);
int startY;
if (centerY < 0) {
startY = centerY + (ny * gridSize);
xVisible = false;
} else {
startY = centerY - (ny * gridSize);
if (centerY > CANVAS_HEIGHT) {
xVisible = false;
}
}
// Draw all vertical lines
for (int i = startX; i <= CANVAS_WIDTH; i = i + gridSize) {
if (i == centerX) {
// Don't draw the y axis, we will draw this on top of all lines.
continue;
} else if (Math.abs(i - centerX) % highlightedLineDistanceX == 0) {
gc.setStroke(Color.rgb(128, 128, 128));
} else {
gc.setStroke(Color.LIGHTGRAY);
}
gc.strokeLine(i, 0, i, CANVAS_HEIGHT);
}
// Draw all horizontal lines
for (int i = startY; i <= CANVAS_HEIGHT; i = i + gridSize) {
if (i == centerY) {
// Don't draw the x axis, we will draw this on top of all lines.
continue;
} else if (Math.abs(i - centerY) % highlightedLineDistanceY == 0) {
gc.setStroke(Color.rgb(128, 128, 128));
} else {
gc.setStroke(Color.LIGHTGRAY);
}
gc.strokeLine(0, i, CANVAS_WIDTH, i);
}
// Draw x-axis and y-axis
gc.setStroke(Color.BLACK);
if (xVisible) {
gc.strokeLine(0, centerY, CANVAS_WIDTH, centerY); // x-axis
}
if (yVisible) {
gc.strokeLine(centerX, 0, centerX, CANVAS_HEIGHT); // y-axis
}
// Draw labels
gc.setFill(Color.BLACK);
gc.fillText("0", centerX + 5, centerY + 15); // Origin label
gc.fillText("x", CANVAS_WIDTH - 10, centerY + 15); // x-axis label
gc.fillText("y", centerX + 5, 15); // y-axis label
// Draw labels on x axis
for (int i = startX; i <= CANVAS_WIDTH; i = i + gridSize) {
if (Math.abs(i - centerX) % highlightedLineDistanceX == 0 && i != centerX) {
String val = String.valueOf((i - centerX) / highlightedLineDistanceX);
if (xVisible) {
// Labels on X axis
gc.strokeLine(i, centerY - 5, i, centerY + 5);
gc.fillText(val, i - 3, centerY + 20);
} else if (showOverflowX) {
// Labels on top edge
gc.strokeLine(i, 0, i, 5);
gc.fillText(val, i - 3, 20);
// Labels on bottom edge
gc.strokeLine(i, CANVAS_HEIGHT - 5, i, CANVAS_HEIGHT);
gc.fillText(val, i - 3, CANVAS_HEIGHT - 10);
}
}
}
// Draw labels on y axis
for (int i = startY; i <= CANVAS_HEIGHT; i = i + gridSize) {
if (Math.abs(i - centerY) % highlightedLineDistanceY == 0 && i != centerY) {
String val = String.valueOf((i - centerY) / highlightedLineDistanceY);
if (yVisible) {
// Labels on Y axis
gc.strokeLine(centerX - 5, i, centerX + 5, i);
gc.fillText(val, centerX + 10, i);
} else if (showOverflowY) {
// Labels on left edge
gc.strokeLine(0, i, 5, i);
gc.fillText(val, 10, i);
// Labels on right edge
gc.strokeLine(CANVAS_WIDTH - 5, i, CANVAS_WIDTH, i);
gc.fillText(val, CANVAS_WIDTH - 20, i);
}
}
}
// Draw mouse position on grid
if (mouseX > 0 && showMousePos) {
gc.setFill(Color.RED);
int realX = (int) ((centerX - mouseX) / scale.get()) * -1;
int realY = (int) ((centerY - mouseY) / scale.get()) * -1;
gc.fillText(realX + ", " + realY, mouseX + 20, mouseY + 20);
}
}
private Point zoomedCenter() {
return new Point((int) (CANVAS_WIDTH / 3.0 + (xOffset.get() * scale.get())),
(int) (CANVAS_HEIGHT / 7.0 + (yOffset.get() * scale.get())));
}
private Point gridPointAtMouse() {
Point center = zoomedCenter();
int realX = (int) ((center.x - mouseX) / scale.get()) * -1;
int realY = (int) ((center.y - mouseY) / scale.get()) * -1;
return new Point(realX, realY);
}
record Point(int x, int y) {
}
public static void main(String[] args) {
launch(args);
}
}