【问题标题】:3D Scatter Chart JavaFX: How to show legend and measures near axis3D散点图JavaFX:如何在轴附近显示图例和测量值
【发布时间】:2017-06-12 13:27:07
【问题描述】:

Looking this post,我尝试在 javaFX 中实现,但遇到了很多困难,一个 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);
    }
}

【问题讨论】:

  • @James_D 我们正在一起做一个项目。正是因为如此,问题是相同的
  • @James_D 你能帮我们吗?
  • 好的,但请不要转发相同的问题。我无法运行代码来生成上一篇文章中的图像。 (“墙”在某些时候被剔除。)这个想法只是向Axis 添加文本或标签 - 看起来这部分应该比你已经完成的部分更容易,但正如我所说我可以'无法使其正常工作以进行测试。
  • JavaFX Axis 类中有很多方法用于控制刻度线、刻度线标签等。我强烈建议仔细研究the Javadoc

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


【解决方案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 网格窗格...

【讨论】:

  • 感谢您的帮助!这是非常有用的。我试图放置一个 2D 图例和read the discussion。不幸的是,这是我的第一个 3D 项目之一。这些是我的失败测试的链接:link1ink2。如何编辑和更正?
  • 像这样翻译标签将转换为 3D 空间,一个 2D 标签在您移动相机之前看起来是正确的。假设您想要一个不移动或旋转的散点图,那么这会很好。否则,您将需要在移动相机时自动转换 2D 标签。 (即...鼠标处理程序)。
  • @Birdasaur 是的,公平点。如果我没记错的话 Camera 的早期版本不能移动(它不是 Node 的子类,你不能将它添加到场景图中):你只需将相机设置在场景中并如果你想让事物相对于它移动,你可以将变换应用到场景的根部(或它的一部分)。也没有SubScene 类。我在 JavaFX 中使用 3D 的大部分实验都是在可以移动相机之前进行的,因此您不得不独立地转换场景图的不同部分。但是您的方法对于当前版本看起来是正确的。
  • 我绝对没有批评您的部分,这对最终解决方案至关重要。我的一部分感觉就像将我的转换部分添加到您的初始设置中更多的是解决 JavaFX 3D 缺乏功能而不是真正的解决方案。 (对低级渲染调用开放公共访问!)但是它确实有效并且可以在技术上支持任何基于 2D 节点的对象。所以结合你的代码和我的转换可以让人们做一些有趣的 3D 叠加。 (锚定标注、2D 瞄准标线、粒子效果(火焰、烟雾、雨、雾)等)
【解决方案2】:

我知道这个问题已经过时了,但 JavaFX 3D 场景中的 2D 标签是一个经常出现的话题,我从来没有看到它以“正确的方式”得到了回答。

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

正确的做法是使用 OpenGL 或 DirectDraw 文本渲染器,它们会在每个渲染循环通道上重绘标签,但 JavaFX 3D 不允许您访问(当前)。因此,“JavaFX 中的正确方法”是将 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 对象。对于我的应用程序,我只是使用一个非常小的球体,它是看不到的。如果您按照 James_D 的示例进行操作,您可以将球体平移到您平移原始轴标签的相同位置。 标签是您添加到场景中的标准 JavaFX 2D 标签...通常通过 StackPane 使标签浮动在 3D 子场景之上。

现在,每当相机移动/旋转时,都会调用此变换,该变换会在 2D 图层上滑动标签。如果不直接访问底层 GL 或 DD 调用,这几乎是在 JavaFX 3D 中执行此类操作的唯一方法,但效果很好。

这里有一个video example 它正在工作。 这是实现浮动二维标签的简单版本的open source example。 (警告,我是该示例的特约作者,而不是试图推广该库。)

【讨论】:

    猜你喜欢
    • 2015-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-03
    相关资源
    最近更新 更多