3D散点图JavaFX:如何在轴附近显示图例和度量

时间:2017-01-26 22:37:40

标签: java javafx scatter-plot javafx-3d scatter3d

Looking this post,我试图在javaFX中实现,有很多困难,Scatter Chart 3D,其中网格是我的x,y和z轴,球体是我的点。

如何沿轴设置图例,轴标签和范围编号?我只能使用没有外部库的javaFX。 我绝望了......我试了好几天......没有结果 请帮我 感谢。

代码

    import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class GraphingData extends Application {

    private static Random rnd = new Random();

    // size of graph
    int graphSize = 400;

    // variables for mouse interaction
    private double mousePosX, mousePosY;
    private double mouseOldX, mouseOldY;

    private final Rotate rotateX = new Rotate(150, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(120, Rotate.Y_AXIS);

    @Override
    public void start(Stage primaryStage) {

        // create axis walls
        Group grid = createGrid(graphSize);

        // initial cube rotation
        grid.getTransforms().addAll(rotateX, rotateY);

        // add objects to scene
        StackPane root = new StackPane();
        root.getChildren().add(grid);

        root.setStyle( "-fx-border-color: red;");
        // create bars
        double gridSizeHalf = graphSize / 2;
        double size = 30;

        //Drawing a Sphere  
        Sphere sphere = new Sphere();  

        //Setting the properties of the Sphere
        sphere.setRadius(10.0);  

        sphere.setTranslateX(-50);
        sphere.setTranslateY(-50);      

        //Preparing the phong material of type specular color
        PhongMaterial material6 = new PhongMaterial();  

        //setting the specular color map to the material
        material6.setDiffuseColor(Color.GREEN);

        sphere.setMaterial(material6);

        grid.getChildren().addAll(sphere);

        // scene
        Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
        scene.setCamera(new PerspectiveCamera());

        scene.setOnMousePressed(me -> {
            mouseOldX = me.getSceneX();
            mouseOldY = me.getSceneY();
        });
        scene.setOnMouseDragged(me -> {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
            rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;

        });

        makeZoomable(root);

        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    /**
     * Axis wall
     */
    public static class Axis extends Pane {

        Rectangle wall;

        public Axis(double size) {

            // wall
            // first the wall, then the lines => overlapping of lines over walls
            // works
            wall = new Rectangle(size, size);
            getChildren().add(wall);

            // grid
            double zTranslate = 0;
            double lineWidth = 1.0;
            Color gridColor = Color.RED;

            for (int y = 0; y <= size; y += size / 10) {

                Line line = new Line(0, 0, size, 0);
                line.setStroke(gridColor);
                line.setFill(gridColor);
                line.setTranslateY(y);
                line.setTranslateZ(zTranslate);
                line.setStrokeWidth(lineWidth);

                getChildren().addAll(line);

            }

            for (int x = 0; x <= size; x += size / 10) {

                Line line = new Line(0, 0, 0, size);
                line.setStroke(gridColor);
                line.setFill(gridColor);
                line.setTranslateX(x);
                line.setTranslateZ(zTranslate);
                line.setStrokeWidth(lineWidth);

                getChildren().addAll(line);

            }

        }

        public void setFill(Paint paint) {
            wall.setFill(paint);
        }

    }

    public void makeZoomable(StackPane control) {

        final double MAX_SCALE = 20.0;
        final double MIN_SCALE = 0.1;

        control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {

            @Override
            public void handle(ScrollEvent event) {

                double delta = 1.2;

                double scale = control.getScaleX();

                if (event.getDeltaY() < 0) {
                    scale /= delta;
                } else {
                    scale *= delta;
                }

                scale = clamp(scale, MIN_SCALE, MAX_SCALE);

                control.setScaleX(scale);
                control.setScaleY(scale);

                event.consume();

            }

        });

    }

    /**
     * Create axis walls
     *
     * @param size
     * @return
     */
    private Group createGrid(int size) {

        Group cube = new Group();

        // size of the cube
        Color color = Color.LIGHTGRAY;

        List<Axis> cubeFaces = new ArrayList<>();
        Axis r;

        // back face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(-0.5 * size);
        r.setTranslateZ(0.5 * size);

        cubeFaces.add(r);

        // bottom face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(0);
        r.setRotationAxis(Rotate.X_AXIS);
        r.setRotate(90);

        cubeFaces.add(r);

        // right face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
        r.setTranslateX(-1 * size);
        r.setTranslateY(-0.5 * size);
        r.setRotationAxis(Rotate.Y_AXIS);
        r.setRotate(90);

        // cubeFaces.add( r);

        // left face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
        r.setTranslateX(0);
        r.setTranslateY(-0.5 * size);
        r.setRotationAxis(Rotate.Y_AXIS);
        r.setRotate(90);

        cubeFaces.add(r);

        // top face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(-1 * size);
        r.setRotationAxis(Rotate.X_AXIS);
        r.setRotate(90);

        // cubeFaces.add( r);

        // front face
        r = new Axis(size);
        r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
        r.setTranslateX(-0.5 * size);
        r.setTranslateY(-0.5 * size);
        r.setTranslateZ(-0.5 * size);

        // cubeFaces.add( r);

        cube.getChildren().addAll(cubeFaces);

        return cube;
    }

    public static double normalizeValue(double value, double min, double max, double newMin, double newMax) {

        return (value - min) * (newMax - newMin) / (max - min) + newMin;

    }

    public static double clamp(double value, double min, double max) {

        if (Double.compare(value, min) < 0)
            return min;

        if (Double.compare(value, max) > 0)
            return max;

        return value;
    }

    public static Color randomColor() {
        return Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));
    }

    public static void main(String[] args) {
        launch(args);
    }
}

2 个答案:

答案 0 :(得分:1)

这是在轴上创建一些测量的基本思路。它不是生产就绪的,但应该给你足够的开始。

private Group createGrid(int size) {

    // existing code omitted...

    cube.getChildren().addAll(cubeFaces);


    double gridSizeHalf = size / 2;
    double labelOffset = 30 ;
    double labelPos = gridSizeHalf - labelOffset ;

    for (double coord = -gridSizeHalf ; coord < gridSizeHalf ; coord+=50) {
        Text xLabel = new Text(coord, labelPos, String.format("%.0f", coord));
        xLabel.setTranslateZ(labelPos);
        xLabel.setScaleX(-1);
        Text yLabel = new Text(labelPos, coord, String.format("%.0f", coord));
        yLabel.setTranslateZ(labelPos);
        yLabel.setScaleX(-1);
        Text zLabel = new Text(labelPos, labelPos, String.format("%.0f", coord));
        zLabel.setTranslateZ(coord);
        cube.getChildren().addAll(xLabel, yLabel, zLabel);
        zLabel.setScaleX(-1);
    }

    return cube;
}

我只想在图表外放置一个图例,这只是一个不旋转的2D网格窗格......

答案 1 :(得分:0)

我知道这个问题已经过时了但是JavaFX 3D场景中的2D标签是一个很多的主题,我从来没有看到它回答过#34;正确的方式&#34;。

在James_D的答案中翻译标签会将3D标签转换为3D标签,在您移动相机之前,该标签看起来是正确的。假设您想要一个不会移动或旋转的散点图,那么这将没问题。另外,您需要在移动相机时自动转换2D标签。 (即......鼠标处理程序)。您可以删除散点图并每次将整个内容读取到场景中,但这将在您的堆内存中被谋杀,并且对于任何实际有用大小的数据集都不可行。

正确的方法是使用OpenGL或DirectDraw文本渲染,重写每个渲染循环传递上的标签,但JavaFX 3D不会授予您访问权限(当前)。所以在JavaFX中正确的方式&#34;是将2D标签浮动到3D子场景的顶部,然后在相机移动时进行平移。这要求您将想要标签的3D位置的3D坐标投影转换为2D屏幕投影。

要在JavaFX 3D中一般管理连接到Point3D的2D标签,您需要进行以下转换:

Point3D coordinates = node.localToScene(javafx.geometry.Point3D.ZERO);
SubScene oldSubScene = NodeHelper.getSubScene(node);
coordinates = SceneUtils.subSceneToScene(oldSubScene, coordinates);
double x = coordinates.getX();
double y = coordinates.getY();
label.getTransforms().setAll(new Translate(x, y));

节点是3D子场景中已有的实际3D对象。对于我的应用程序,我只使用一个极小的Sphere,它是无法看到的。如果您要关注James_D的示例,您可以将球体转换为您翻译原始轴标签的相同位置。 标签是您添加到场景中的标准JavaFX 2D标签...通常通过StackPane使标签浮动在3D子场景的顶部。

现在,无论何时相机移动/旋转,都会调用此变换,从而在2D图层上滑动标签。如果没有直接访问底层的GL或DD调用,这几乎是在JavaFX 3D中执行此类操作的唯一方法,但它运行良好。

这是video example的工作原理。 这是实现浮动2D标签的简单版本的open source example。 (警告,我是该样本的撰稿人,而不是尝试推广该库。)