Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

From Nonsense Mathematics to Awesome Visualization

DZone's Guide to

From Nonsense Mathematics to Awesome Visualization

How can you make math fun? Visualize it. Let's use JavaFX to bring math to life, bridging the gap between theories and code.

· Java Zone
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

Visualization is always a good way to learn things faster and make sense of conceptual things such as algorithms, mathematical formulas, or physical phenomena. You may have seen a lot of visualizations created by JavaScript and some other tools other than Java. Surely, they were more beautiful in comparison with Applets or Swing-based applications. That was true in the era before JavaFX!

Nowadays, you can create fancier UIs with JavaFX more easily. In this post, I am going to show you how to convert an abstract mathematical formula to an awesome JavaFX 3D visualization.

Image title

Inspiration

At JavaOne 2013, Michael Hoffer explained his awesome 3D visualization. At that time, it seemed a hard task for me to create program likes that, but It was lightened my mind that It is possible to create such a things with JavaFX.


Nonsense Theory

The vibration of a string of a Guitar is an example of one-dimensional vibration. Playing drum is an example of two-dimensional vibration. Mathematics explains both of them in detail, but let me show a simpler example, Rectangular Membrane!

I am not good at theories and formulas, so, for the sake of simplicity, I am not going to explain the mathematical details of this phenomena. If you are interested, you can read this link for further information.

We let u(x, y, t) = the deflection of a membrane from equilibrium at position (x, y) and time t.
For a fixed t, the surface z = u(x, y, t) gives the shape of the membrane at time t. Under ideal assumptions (e.g. uniform membrane density, uniform tension, no resistance to motion, small deflection, etc.), one can show that u satisfies the two-dimensional wave equation:

Image titlefor 0 < x < a, 0 < y < b

Where the constant c has the units of velocity. It is given by:

Image titleWhere τ is the tension per unit length, and ρ is mass density.

Nonsense Formula

So if we continue our mathematics and apply our boundary conditions and some relaxations, we will reach to this formula:

Image titleWhere:

Image title

The Awesome Visualization

Now we have a formula that we have no idea about! We only know that it shows how a rectangular membrane (a * b) vibrates. How do the parameters c, m, or n affects the vibration? It’s not hard to answer this question using pure mathematics, but that wouldn’t make any sense for those who are not good with theories!

That’s where visualization came to the scene! So how to visualize it?

FXyz3D: A JavaFX 3D Visualization Library:

Working with the JavaFX 3D API is a very hard task, especially if you are not familiar with 3D graphic rendering concepts. But there is always some library or framework that makes your job easy. In order to get familiar with FXyz, you should see the samples in this link and especially read this one.

Before we move further, You should change the source code of FXyz, because by default, FXyz plots are surface symmetric, which means that if you want to plot a surface 2 (x-axis from 0 to 2) by 3 (y-axis from 0 to 3), it will plot for you 4 (x-axis from -2 to 2) by 6 (y-axis from -3 to 3) so you should edit the following code:

private TriangleMesh createPlotMesh(Function < Point2D, Number > function2D, double rangeX, double rangeY, int divisionsX, int divisionsY, double scale) {
    ...
    // Create points
    for(int y = 0; y <= divisionsY; y++) {
            float dy = (float)(-rangeY / 2 d + ((float) y / (float) divisionsY) * rangeY);
            for (int x = 0; x <= divisionsX; x++) {
                float dx = (float)(-rangeX / 2 d + ((float) x / (float) divisionsX) * rangeX);
                pointY = (float) scale * function2D.apply(new Point2D(dx, dy)).floatValue();
                listVertices.add(new Point3D(dx, pointY, dy));
            }
        }
        ...
}


To:

private TriangleMesh createPlotMesh(Function < Point2D, Number > function2D, double rangeX, double rangeY, int divisionsX, int divisionsY, double scale) {
    ...
    // Create points
    for(int y = 0; y <= divisionsY; y++) {
            float dy = (float)(((float) y / (float) divisionsY) * rangeY);
            for (int x = 0; x <= divisionsX; x++) {
                float dx = (float)(((float) x / (float) divisionsX) * rangeX);
                pointY = (float) scale * function2D.apply(new Point2D(dx, dy)).floatValue();
                listVertices.add(new Point3D(dx, pointY, dy));
            }
        }
        ...
}


Now we can develop our visualization.

Program Code

public class MathVisualizationExample extends Application {
    private final double a = 1;
    private final double b = 2;
    private final double c = 1;
    private final int m = 2;
    private final int n = 1;

    private double time = 0;

    private BiFunction<Double, Double, Double> functionGenerator(double time) {
        final double k = Math.PI * Math.sqrt(Math.pow(n / a, 2) + Math.pow(m / b, 2));
        return (pX, pY) -> {
            return Math.sin(n * Math.PI * pX / a)
                    * Math.sin(m * Math.PI * pY / b)
                    * Math.cos(c * k * time);
        };
    }

    private Function<Point2D, Number> generateFunction(double time) {
        return (Point2D p) -> {
            return functionGenerator(time).apply(p.getX(), p.getY());
        };
    }

    @Override
    public void start(Stage primaryStage) {
        Group sceneRoot = new Group();
        Scene scene = new Scene(sceneRoot, 800, 800, true, SceneAntialiasing.BALANCED);
        PerspectiveCamera camera = new PerspectiveCamera(true);
        //setup camera transform for rotational support
        CameraTransformer cameraTransform = new CameraTransformer();
        cameraTransform.setTranslate(1, 0, -5);
        cameraTransform.getChildren().add(camera);
        //add a Point Light for better viewing of the grid coordinate system
        PointLight light = new PointLight(Color.WHITE);
        cameraTransform.getChildren().add(light);
        light.setTranslateX(camera.getTranslateX());
        light.setTranslateY(camera.getTranslateY());
        light.setTranslateZ(camera.getTranslateZ());
        scene.setCamera(camera);

        Group group = new Group();
        group.getChildren().add(cameraTransform);

        SurfacePlotMesh surface = new SurfacePlotMesh(generateFunction(0), a, b, 100, 100, 1);
        surface.getTransforms().addAll(new Rotate(200, Rotate.X_AXIS),
                new Rotate(60, Rotate.Y_AXIS));
        surface.setCullFace(CullFace.NONE);
        surface.setTextureModePattern(CarbonPatterns.LIGHT_CARBON, 1.0d);

        KeyFrame keyFrame = new KeyFrame(Duration.millis(50), (ActionEvent event) -> {
            surface.setFunction2D(generateFunction(time));
            time += 0.005;
        });
        Timeline timeLine = new Timeline(keyFrame);
        timeLine.setCycleCount(Timeline.INDEFINITE);
        timeLine.play();
        group.getChildren().addAll(surface);

        sceneRoot.getChildren().addAll(group);

        primaryStage.setTitle("Awesome Visualization");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}


I tried to write the code in the simplest form. If you want to change the parameters and run the program, you can find the source code here.

Code Explanation

        Group sceneRoot = new Group();
        Scene scene = new Scene(sceneRoot, 800, 800, true, SceneAntialiasing.BALANCED);
        PerspectiveCamera camera = new PerspectiveCamera(true);
        //setup camera transform for rotational support
        CameraTransformer cameraTransform = new CameraTransformer();
        cameraTransform.setTranslate(1, 0, -5);
        cameraTransform.getChildren().add(camera);
        //add a Point Light for better viewing of the grid coordinate system
        PointLight light = new PointLight(Color.WHITE);
        cameraTransform.getChildren().add(light);
        light.setTranslateX(camera.getTranslateX());
        light.setTranslateY(camera.getTranslateY());
        light.setTranslateZ(camera.getTranslateZ());
        scene.setCamera(camera);

        Group group = new Group();
        group.getChildren().add(cameraTransform);

        SurfacePlotMesh surface = new SurfacePlotMesh(generateFunction(0), a, b, 100, 100, 1);
        surface.getTransforms().addAll(new Rotate(200, Rotate.X_AXIS),
                new Rotate(60, Rotate.Y_AXIS));
        surface.setCullFace(CullFace.NONE);
        surface.setTextureModePattern(CarbonPatterns.LIGHT_CARBON, 1.0d);


This part of the code is the initialization of 3D cameras and angles, please don’t afraid if you are not familiar with Camera, CameraTransformer, etc., just edit/comment some lines and parameters to see how it affects the program. The most important thing is SurfacePlotMesh.
In order to plot our surface, we need to provide a Function2D<Point2D, Number> for SurfacePlotMesh. With our little modification in FXyz core, we are drawing our plot from [0 to a] in the x-axis and from [0 to b] in the y-axis. Other parameters are used for rendering the surface.

    private final double a = 1;
    private final double b = 2;
    private final double c = 1;
    private final int m = 2;
    private final int n = 1;

    private double time = 0;

    private BiFunction<Double, Double, Double> functionGenerator(double time) {
        final double k = Math.PI * Math.sqrt(Math.pow(n / a, 2) + Math.pow(m / b, 2));
        return (pX, pY) -> {
            return Math.sin(n * Math.PI * pX / a)
                    * Math.sin(m * Math.PI * pY / b)
                    * Math.cos(c * k * time);
        };
    }

    private Function<Point2D, Number> generateFunction(double time) {
        return (Point2D p) -> {
            return functionGenerator(time).apply(p.getX(), p.getY());
        };
    }


This is where the surface Function2D is generating. I just convert our mathematic formula to Java code.

Our visualization depends on time, so in order to make our visualization come alive, we need JavaFX Animation:

        KeyFrame keyFrame = new KeyFrame(Duration.millis(50), (ActionEvent event) -> {
            surface.setFunction2D(generateFunction(time));
            time += 0.005;
        });
        Timeline timeLine = new Timeline(keyFrame);
        timeLine.setCycleCount(Timeline.INDEFINITE);
        timeLine.play();


This is where we say every 50 milliseconds, update the surface and render it again.

Final Words

As you saw, I developed an Awesome JavaFX 3D visualization with the help of the FXyz library with few lines of code. I'll give you a homework: visualize a Circular Membrane! If you want to see more JavaFX mathematical visualizations, you can check out KNTU PDE MathTools or ODE MathTools.
You can see the full version of this visualization demo in this video:


Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:
java ,javafx ,visualization ,mathematics ,tutorial

Published at DZone with permission of Hossein Rimaz. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}