JavaFX如何在不改变线宽的情况下缩放Path的坐标?

时间:2018-01-29 15:53:16

标签: javafx javafx-8 scale transformation

我目前正在制作具有缩放和平移功能的步骤折线图。由于我需要处理的数据量非常大,因此每次调用layoutPlotChildren()时都无法重新创建步骤行的整个路径。所以我的想法是创建一次路径元素,然后在缩放平移事件时将其转换。

到目前为止,一切都按计划进行,但是当我试图扩展路径时,我遇到了一个大问题。由于Path类是从Shape继承的,因此Graph上的缩放不仅可以转换路径的坐标,还可以改变其厚度。

我创建了一个最小的工作示例来说明问题:

public class Sandbox extends Application
{

    @Override public void start(Stage primaryStage)
    {
        Path path = new Path();
        path.getElements().add(new MoveTo(0, 0));
        path.getElements().add(new LineTo(100, 0));
        path.getElements().add(new LineTo(100, 100));
        path.getElements().add(new LineTo(200, 100));

        path.setScaleY(3);

        Pane pane = new StackPane(path);

        Scene scene = new Scene(pane, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

如您所见,坐标按预期缩放,但所有水平线的厚度均为之前的三倍。我完全明白,为什么会发生这种情况,但我想不出任何可能阻止线条变粗的可能性。您是否有任何关于我如何解决这个问题的建议,同时仍然保证可接受的性能?

3 个答案:

答案 0 :(得分:1)

请注意,这不是Shape类的问题,而是缩放实际执行的问题:Node缩放,每个距离都会缩放x x {{只要在未缩放的Node中的1}}倍。这包括任何线条的厚度。

但是,您可以通过将坐标与比例因子相乘来修改路径元素的坐标:

// class for holing a pair of (x, y) properties with method for modifying the values based on scale
public static class PointScaler {

    private final DoubleProperty xProperty;
    private final DoubleProperty yProperty;
    private final double x0;
    private final double y0;

    public PointScaler(DoubleProperty xProperty, DoubleProperty yProperty) {
        this.xProperty = xProperty;
        this.yProperty = yProperty;

        // save untransformed values
        this.x0 = xProperty.get();
        this.y0 = yProperty.get();
    }

    public void setScale(double factor) {
        xProperty.set(factor * x0);
        yProperty.set(factor * y0);
    }

}

private double scaleFactor = 1;

@Override
public void start(Stage primaryStage) {
    Path path = new Path();
    MoveTo m0 = new MoveTo(0, 0);
    LineTo l0 = new LineTo(100, 0);
    LineTo l1 = new LineTo(100, 100);
    LineTo l2 = new LineTo(200, 100);
    path.getElements().addAll(m0, l0, l1, l2);

    // create list with scaler for each (x,y) pair in the path elements
    List<PointScaler> scalers = new ArrayList<>();
    scalers.add(new PointScaler(m0.xProperty(), m0.yProperty()));
    scalers.add(new PointScaler(l0.xProperty(), l0.yProperty()));
    scalers.add(new PointScaler(l1.xProperty(), l1.yProperty()));
    scalers.add(new PointScaler(l2.xProperty(), l2.yProperty()));

    Pane pane = new StackPane(path);

    Scene scene = new Scene(pane, 800, 600);

    // change scale on click of mouse buttons
    scene.setOnMouseClicked(evt -> {
        if (evt.getButton() == MouseButton.PRIMARY) {
            scaleFactor *= 1.2;
        } else if (evt.getButton() == MouseButton.SECONDARY) {
            scaleFactor /= 1.2;
        } else {
            return;
        }
        // apply scale to path points
        for (PointScaler scaler : scalers) {
            scaler.setScale(scaleFactor);
        }
    });

    primaryStage.setScene(scene);
    primaryStage.show();
}

请注意,setScale方法保持x和y比例相同但不应太难修改方法以适用于不同的转换。请注意,某些PathElement CubicCurveToQuadCurveTo需要多个PointScaler个实例。

答案 1 :(得分:1)

基于fabians great response,我创建了自己的解决方案,这是步骤行的简化版本(我的初始问题),但不像上述响应那样灵活。为了完整起见,我会在这里发布我的答案,但我仍然认为法比亚人的答案必须是公认的答案。

public class Sandbox extends Application
{

    private DoubleProperty scaleX = new SimpleDoubleProperty(1d);
    private DoubleProperty scaleY = new SimpleDoubleProperty(1d);

    @Override public void start(Stage primaryStage)
    {
        Path path = new Path();
        path.getElements().add(new MoveTo(0, 0));

        path.getElements().add(createLineTo(100, 0));
        path.getElements().add(createLineTo(100, 100));
        path.getElements().add(createLineTo(200, 100));

        scaleY.setValue(3);

        Pane pane = new StackPane(path);
        Scene scene = new Scene(pane, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private LineTo createLineTo(double x, double y)
    {
        LineTo lineTo = new LineTo();
        lineTo.xProperty().bind(scaleX.multiply(x));
        lineTo.yProperty().bind(scaleY.multiply(y));
        return lineTo;
    }
}

这主要是为了防止foreach循环和创建一个单独的类的需要,但如前所述,这并不像fabians所说的那样多才多艺。

答案 2 :(得分:0)

另一种方法是将反比例应用于每个Shape的strokeWidthProperty。这可以通过绑定轻松完成。 因此,如果您的路径按s = 3缩放,例如您必须将strokeWidthProperty绑定到w * 1/3,其中w是标称笔划宽度。