© Stephen Chin, Johan Vos and James Weaver 2019
S. Chin et al.The Definitive Guide to Modern Java Clients with JavaFXhttps://doi.org/10.1007/978-1-4842-4926-0_14

14. Scientific Applications Using JavaFX

Stephen Chin1 , Johan Vos2 and James Weaver3
(1)
Belmont, CA, USA
(2)
Leuven, Belgium
(3)
Marion, IN, USA
 

Written by Johan Vos

Many of the spectacular advances that are being achieved recently are in the field of data sciences, or at least related to it. Different forms of machine learning, big data calculations, and quantum computing are rapidly getting an increased impact on society in general.

The technical foundations of these advances are based on different scientific research areas. As is often the case, technical foundations are implementation agnostic. Different languages, platforms, and binaries can be used to implement the technical foundations.

In this chapter, we will explain why Java in general, and client Java (including JavaFX) in particular, is a great choice for developers who want to create scientific applications, using the language they master.

We will first demonstrate a few real samples and then explain a more general way that allows you to create scientific applications in Java, including doing the research work related to the scientific application.

JavaFX for Space Exploration

A great scientific application using JavaFX technologies is the Deep Space Trajectory Explorer, or DSTE. This is a product created by a.i. solutions and used by NASA. The product is described on the web site of a.i. solutions at https://ai-solutions.com/deep-space-trajectory-explorer.

According to the web site, the Deep Space Trajectory Explorer is an interactive software package that combines cutting-edge multibody trajectory design techniques with innovative visualizations to dramatically reduce time spent on trajectory design.

With DSTE, users can design trajectories of objects in space, which typically requires heavy calculations. Often, those calculations are done without any visualization or interactivity until the calculation is done. The DSTE product makes the design process more “agile” and interactive.

One of the key benefits of DSTE is that it allows the usage of interactive visualization in the design process for missions. Doing so, it becomes easier and more intuitive to select orbits that satisfy specific mission constraints.

The requirement to combine high-performant computing and complex visualization is a great use case for Java and JavaFX. Java itself is very scalable, and there are plenty of powerful Java APIs and framework that help developers and operators to scale Java code in multicore environments. The JavaFX platform allows for visualization including 3D models and Canvas rendering and thereby uses hardware-accelerated rendering, leveraging the availability of the GPU. Since JavaFX is pure Java, this rendering can easily be integrated with the pieces of code that are responsible for the high-performant computations.

The Deep Space Trajectory Explorer leverages the performance offered by the JavaFX platform in a number of ways. It contains different views, both in 2D and 3D. Many views allow for click-and-drag functionality. Canvas components are used to render millions of linked data points onscreen, without freezing the layout. Filters allow to select and deselect a number of options, and the resulting changes are real time rendered in the views.

Figure 14-11 shows a screenshot generated by the DSTE product that shows some of the views offered by the tool.
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig1_HTML.jpg
Figure 14-1

Screenshots generated by DSTE

The Deep Space Trajectory Explorer is not an application that is easily created. It requires deep knowledge both about the physics behind orbits, about high-performance computing, and about UI development. JavaFX is a crucial component in the total solution, proving the power of the platform.

JavaFX for Quantum Computing

Quantum computing is rapidly gaining interest, both in scientific environments and in IT departments.

One of the promises of quantum computing is that some problems that are extremely hard or practically impossible to solve using classic computers can easily be tackled with quantum computers. Especially in areas where algorithms show exponential time behavior, quantum computers can make a big difference.

Quantum computers use some core concepts that are fundamentally present in nature, but not in classical computers.

In a classic computer, the most granular unit is a bit. A bit is either 0 or 1. In a quantum computer, the most granular unit is a qubit. A qubit can hold the value 0 or 1, but it can also be in a so-called superposition state, in which it holds a linear combination of 0 and 1. When measured though, a qubit always returns either 0 or 1. Therefore, the algorithms for quantum computing must take advantage of the superposition states without measuring the qubits during the processing.

In classic computing, bits are manipulated by gates. For example, the NOT gate will flip the value of a bit. When the bit was 0 before it entered the gate, the result after the gate will be 1 and vice versa.

Similarly, in quantum computing, qubits are manipulated by quantum gates.

While there are some very early experimental chips for quantum computers available, quantum computing is by no means ready yet for mainstream development. The practical requirements to create a quantum computer with sufficient qubits that stay available for a reasonable amount of time are huge. Therefore, only a few prototypes exist with a limited amount of qubits.

However, because of the huge potential impact of quantum computing, many developers are already working on algorithms that might benefit from quantum computing. Typically, local or cloud-based simulators are used to develop these algorithms, and some companies are now starting to offer real quantum computers as a cloud service.

These algorithms are often developed in a programming language and visualized using circuit visualization.

One of those quantum simulators, Strange, is using a companion tool called StrangeFX which is build using JavaFX to render the circuits. StrangeFX is available on GitHub at https://github.com/gluonhq/strangefx.

StrangeFX allows developers to drag quantum gates onto qubit wires. While they do this, the local simulator evaluates the circuit and shows the results in real time. The drag-and-drop capabilities of JavaFX provide a very intuitive way for developing quantum circuits, and the integration with the quantum simulation algorithms is straightforward since both components are written in Java.

Figure 14-2 shows a simple screenshot of StrangeFX, showing three qubits and a number of gates in a toolbar that can be dragged to the qubit wires.
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig2_HTML.jpg
Figure 14-2

StrangeFX allows developers to drag quantum gates onto qubit wires

A more complex example includes a simulation of Grover’s search shown in Figure 14-3, a famous quantum algorithm.
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig3_HTML.jpg
Figure 14-3

A simulation of Grover’s search

Using JShell

In the not so far past, we saw a fault line between activities in two different areas: developers on one hand, using programming languages to create applications, and people working on infrastructure and operations. Developers were mainly working on business solutions for a particular problem in an isolated environment. Once the problem was solved, the solution was handed over to the IT department, who had to bring it into productions. The gap between those worlds caused lots of issues related to scalability, documentation, versioning, accountability, dependencies, and so on. Too often the “it works for me” situation blocked the transition from developers who created a business solution toward operational people bringing the solution to high-scale production.

This gap is now typically being tackled by the so-called “DevOps” approach, where some overlapping parts of development and operations are brought together in one approach or team. A number of software improvements, including containerization, allowed for development and operations to work more closely with each other as shown in Figure 14-4.
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig4_HTML.png
Figure 14-4

The devops approach

Overlapping parts of development and operations are tackled in a cross-domain devops environment.

Today, the growing interest in data sciences shows a new fault line between two groups: people working on research and developers working on development in production. Very often, the research is done by scientists who have extremely difficult and complex scientific problems to solve. Those researchers should focus on the core problem and not on the syntax or specific behavior of specific programming languages. Therefore, great scientific platforms or languages like Matlab and Python are often used by scientists to tackle those core problems.

Once the core problem is solved, though, it often needs to be integrated in a product and brought into production. This often creates new issues. Scientific platforms are focused on helping researchers finding the best solution to a scientific problem, not on finding the best way to integrate with databases, web services, and high-availability and security services.

The latter are areas where Java excels in. However, when scientists would have to use the same environment as Java Enterprise developers, their productivity would most likely drop.

When doing pure research, the iteration cycles are very different from developing business applications and running integration tests. During research, scientists want to measure the impact of changing a single variable, or they want to add a new layer to an existing deep learning model. They should be able to inspect the parameters and intermediate values of their algorithms. This is different from debugging business applications. It requires faster and deeper interactions with the algorithm itself, without the need of recompiling applications or running unit tests.

In the following, we will show how modern Java allows for this rapid scientific development, thanks to JShell. The JShell tool is included in the Java SE distributions starting from Java 9. It is a so-called REPL, which is an acronym for “read-eval-print-loop,” and it provides developers with a simple and interactive environment for creating and inspecting applications and algorithms. JShell is built on top of Java, and you can leverage all Java APIs with JShell, including the JavaFX APIs.

Therefore, JShell is a great tool allowing the transition between scientific research and development for production as illustrated in Figure 14-5.
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig5_HTML.png
Figure 14-5

JShell allows for a transition between the scientific development and the integration in business environments

We will first show the basic functionalities of JShell. Next, we will show how you can easily work with linear algebra in JShell, using the ND4J library. Finally, we will demonstrate how using JavaFX with JShell allows for real easy and fast visualization during prototyping.

Using JShell

Starting JShell is very easy. JShell is a tool located in the same directory as the javac and java wrapper. Hence, if you managed to add java and javac to your path, simply invoking
jshell

should start the tool. Once JShell is started, you enter the JShell environment in which you can enter commands or statements.

After starting JShell, you see the following:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figa_HTML.jpg
JShell allows you to create Java statements. For example, the following statement can be written after the JShell prompt
System.out.println("Hello, JShell");

It will result immediately in the following response:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figb_HTML.jpg
While this looks very similar to how a regular Java application is created, it is important to notice that we didn’t have to create a package or a class or a main method. The following statement is equivalent to creating a class HelloWorld containing the following definition:
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Jshell");
    }
}
Next, we would have to compile the class
javac HelloWorld.java
and run it using
java HelloWorld

(Note: As of Java 11, it is possible to skip the compilation step and directly run the class using java HelloWorld.java)

While the end result is the same (“Hello, JShell” is being printed), the steps are very different. Creating a class and a method with modifiers is very important when working on complex or modular software, and it should not be seen as overhead in this case. However, when we simply want to know what would be printed in the preceding snippet, the approach using JShell is giving us much faster answers.

Using JShell for printing “Hello, JShell” is not very ambitious, but we showed that you can use the same Java syntax in Java applications and in JShell. Hence, the familiarity with Java is one of the key advantages of JShell.

Scientific applications often require mathematical operations. We will now show an example that has more mathematical functionality.

The following snippet will print the value of the sine of a value that takes the value of 0, 30, 60, and 90 degrees:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figc_HTML.jpg
Again, note how similar this snippet is to how a Java application achieving the same would look like. For convenience, such a Java application is shown in the following:
import java.util.stream.*;
public class JshellSin {
    public static void main(String[] args) {
        IntStream.range(0, 4)
            .mapToDouble(i -> 30. * i * 2* Math.PI/360.)
            .forEach(d -> System.out.println(Math.sin(d)));
    }
}

One of the great things about JShell is the integrated editor functionalities. We can use the arrow up/down to move to a previous/next statement and edit that statement.

For example, suppose we made a mistake in our algorithm. Instead of the sine, we wanted to print the cosine.

We can easily do this by hitting the arrow up key once, which will show the previous line again, and we can modify it so that we replace “Math.sin” with “Math.cos.”

Hitting return immediately reevaluates the expression and prints the results:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figd_HTML.jpg

As such, what JShell is allowing us here is to experiment with our statements or algorithm, by editing statements and immediately getting feedback about the results.

This approach is already more similar to how scientists work with Python and Matlab to create algorithms.

A huge advantage of JShell is that the code is 100% compatible with new or existing Java applications. The JShell code can easily be pasted in a Java class.

JShell contains a number of commands for saving and loading snippets. Those snippets can then be pasted in your applications.

Hence, the final resulting work, achieved by a scientist using JShell, can immediately be used by a Java developer who uses an IDE and works on integrating the scientific algorithms with other components in the project. The JShell snippet can be wrapped in a private method, submitted to an executor, surrounded with header parameters including security credentials and more.

While we touched some of the core concepts of JShell, we barely scratched the surface of what is possible. In the remainder of this chapter, we will focus on how JShell can be used in environments that combine scientific work and high-quality visualizations. If you want to learn more about JShell itself, you are recommended to have a look at the official JShell product page at https://docs.oracle.com/en/java/javase/11/jshell/introduction-jshell.html.

About ND4J

The ND4J linear algebra library, which was introduced in the previous chapter, allows for Java developers to access high-performant linear algebra functionality in a way that is very convenient to Java developers.

ND4J provides an abstraction layer on top of the platform-dependent libraries that provide linear algebra tools. The name ND4J refers to N-dimensional linear algebra for Java. Many platforms (e.g., Windows, Mac OS, Linux, iOS, Android) contain linear algebra libraries that are highly optimized for the specific platform. Moreover, the availability of specific hardware (e.g., GPUs) may lead to even more specific implementations of some functionality.

Since performance is very important in the data science area, it is crucial for Java developers to be able to leverage the functionality provided by those native libraries. However, it would be a pain for those developers if they have to write applications that only work for a specific hardware or operating system configuration.

This is where ND4J, and its dependencies, provides a solution. The top layer of ND4J provides APIs that users can interact with. These are Java APIs that are familiar to Java developers, but also to scientists as they provide the typical functionality one can expect from a linear algebra library. Under the hood, those APIs are mapped to the best available native library.

The ND4J library provides lots of value to Java developers who want to work with mathematical functionality, but also to researchers and scientists who want their work to be easily integrated in new or existing Java applications, whether they are running in a big enterprise or cloud environment or on embedded or mobile devices or anything between them.

Before we show how to use ND4J with JShell, we show a very simple application that uses ND4J for doing basic matrix manipulations:

Consider the following sample:
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.factory.Nd4j;
public class HelloNd4j {
    public static void main(String[] args) {
        INDArray a = Nd4j.zeros(3,5);
        System.out.println("Matrix a has 3 rows and 5 columns: "+a);
        System.out.println("++++++++++++++++++++++++++++++ ");
        INDArray b = Nd4j.create(new double[] {0.,1.,2.,3.,4.,5.}, new int[] {2,3});
        INDArray c = Nd4j.create(new double[] {2.,-1.,3.}, new int[] {3,1});
        System.out.println("Matrix b has 2 rows ad 3 columns: "+b);
        System.out.println("++++++++++++++++++++++++++++++ ");
        System.out.println("Vector c has 3 elements: "+c);
        System.out.println("++++++++++++++++++++++++++++++ ");
        INDArray d = b.mmul(c);
        System.out.println("matrix product of b x c  = "+d);
        System.out.println("++++++++++++++++++++++++++++++ ");
    }
}

The ND4J library requires other libraries to be available on the classpath. Since we don’t want to be bothered about exactly what libraries we need, we delegate that part to a build tool, for example, Maven. In a pom.xm file, we declare what we need, and Maven will make sure all related dependencies are downloaded and put on the classpath.

The following pom.xml can be used to achieve this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <packaging>jar</packaging>
  <groupId>org.modernclient</groupId>
  <artifactId>nd4jshell</artifactId>
  <version>1.0.0</version>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>org.nd4j</groupId>
      <artifactId>nd4j-native-platform</artifactId>
      <version>1.0.0-beta3</version>
    </dependency>
    <dependency>
      <groupId>org.openjfx</groupId>
      <artifactId>javafx-controls</artifactId>
      <version>11</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <release>11</release>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <goals>
              <goal>java</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <mainClass>org.modernclient.HelloNd4j</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
If we run this application using
mvn compile exec:java
we see the following output:
Matrix a has 3 rows and 5 columns:
[[         0,         0,         0,         0,         0],
 [         0,         0,         0,         0,         0],
 [         0,         0,         0,         0,         0]]
++++++++++++++++++++++++++++++
Matrix b has 2 rows ad 3 columns:
[[         0,    1.0000,    2.0000],
 [    3.0000,    4.0000,    5.0000]]
++++++++++++++++++++++++++++++
Vector c has 3 elements:
[2.0000,
 -1.0000,
 3.0000]
++++++++++++++++++++++++++++++
matrix product of b x c  =
[5.0000,
 17.0000]
++++++++++++++++++++++++++++++

In this simple application, we created a few matrices and a vector, and we multiplied a matrix and a vector. While these are not extraordinary calculations, they show how ND4J works. If you want to read more about ND4J, and by extension about the projects that are related with it, we recommend to have a look at https://​deeplearning4j.​org/​docs/​latest/​nd4j-overview. In the next section, we will explain how you can easily integrate applications based on ND4J in JShell.

Using ND4J in JShell

We can start JShell with the same classpath as the application that is hosted in the same directory by typing
mvn compile com.github.johnpoth:jshell-maven-plugin:1.1:run
Note that this requires the pom.xml file to be available in the same directory as where we type this command. The pom.xml file contains the dependencies for the application, and based on those dependencies (including transitive dependencies), the JShell-maven-plugin composes the classpath that is provided to JShell. The pom file used for this sample is shown in the following:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <packaging>jar</packaging>
  <groupId>org.modernclient</groupId>
  <artifactId>plotjshell</artifactId>
  <version>1.0.0</version>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>org.openjfx</groupId>
      <artifactId>javafx-controls</artifactId>
      <version>11</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <release>11</release>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
            <executable>java</executable>
            <longModulepath>false</longModulepath>
            <arguments>
                <argument>--module-path</argument>
                <classpath />
                <argument>--add-modules</argument>
                <argument>javafx.controls</argument>
                <argument>-classpath</argument>
                <classpath />
                <argument>org.modernclient.Plot</argument>
            </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

As a result, the command shown in the preceding text will start JShell with the classpath that is also used in the application. First, we import the packages that we require in our application:

../images/468104_1_En_14_Chapter/468104_1_En_14_Fige_HTML.jpg

We now enter the commands from the preceding application one by one. JShell will give immediate output after a single line has been entered. The first command that uses the ND4J API will initialize the ND4J backend, during which the most optimal provider for linear algebraic functions is selected and initialized. The feedback for this is printed before the result of this first command.

In our case, the first command is the creation of a 3 × 5 matrix which contains all zeroes. Entering this command generates the following output:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figf_HTML.jpg

The warning message we see here tells us that we don’t have a logging provider for the SLF4J framework, which is something we do not want to use so we are happy with the warning.

We can now continue entering commands and inspect the output. For example, after entering the first line where we declare the 2 × 3 matrix, we get the following output:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figg_HTML.jpg

After entering the last command (ignoring the System.out.println), the output of the matrix-vector multiplication is shown:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figh_HTML.jpg

This is the same result as we got from our application.

One of the nice things we can now do with JShell is to just build on top of this result. For example, if we want to multiply all elements in the resulting vector with a scalar 3, we enter the command d.mul(3) . The ND4J Javadoc explains that the mul command will multiply the matrix elements by a provided number – see https://deeplearning4j.org/api/latest/org/nd4j/linalg/api/ndarray/INDArray.html#mul-java.lang.Number-.

We do not have to rerun the existing code or recompile an application. We simply type the command, and the result is immediately shown:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figi_HTML.jpg

Finally, we will show some operations you can do using JShell. The examples we show have no scientific meaning, but they should illustrate the flexibility of the JShell tool, compared to the typical development cycle that involves modifying source code in an IDE, recompiling, and running from scratch.

After we executed the commands from the previous sample, we want to create a new function. In this new function, all elements of a matrix (or a vector) are multiplied by a number, and then another number is subtracted from the result.

The function, which we will name someOperation, that does this is defined as follows, in Java syntax:
INDArray someOperation(INDArray src, int m, int s) {
        return src.mul(m).add(-s);
}

Defining functions in JShell is very similar as defining functions in Java applications. We just enter the function definition. JShell will give confirmation about the created function, and from that moment on, we can use the function in all our operations:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figj_HTML.jpg

For example, we can now use this new function on the b matrix that was created before. We will multiply all elements of b with 2 and then subtract 1 of each element. For clarity, we first print the current value of b, which is easily done by simply entering b; at the JShell prompt:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figk_HTML.jpg

Note the “$31” prefixing the result. When no result variable is used, JShell will automatically create a variable itself and assign the result to this variable. Those variables can later be used again, similar to how other variables are used.

USING JavaFX in JShell

Since JShell is built on top of the JVM, any library, framework, or application that runs on the JVM can run using JShell. It may sound overkill to use a REPL to create JavaFX applications, and in many cases it is indeed not recommended. However, the Java visualization technologies in JavaFX allow for rapid visualization of data, which can be very useful when developing a scientific application.

Before we show an example of this rapid visualization, we explain how a JavaFX application can be executed inside JShell. But first, we will show an alternative way of starting stand-alone JavaFX applications.

Starting Stand-Alone JavaFX Code

Typically, a JavaFX application extends javafx.application.Application. Its start method is called by the JavaFX runtime. The JavaFX launcher manages the bootstrap of the JavaFX runtime.

However, we can also directly start the JavaFX runtime and run JavaFX applications inside a single method. The following code snippet shows how to do this:
package org.modernclient;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class StandAlone {
    public static void showHello() {
        Platform.startup(() -> {});
        Platform.setImplicitExit(false);
        Platform.runLater( () -> {
            Label label = new Label ("Hello, standalone JavaFX");
            Button button = new Button ("Click me");
            button.setOnAction(e -> {label.setText("Clicked");});
            button.setTranslateY(50);
            StackPane box = new StackPane();
            box.getChildren().addAll(label, button);
            Scene s = new Scene(box, 200, 200);
            Stage stage = new Stage();
            stage.setTitle("StandAlone Hello");
            stage.setScene(s);
            stage.show();
        });
    }
    public static void main(String[] args) {
        showHello();
    }
}
In this application, the main method calls a static method showHello() which launches the JavaFX runtime manually by calling
Platform.startup(() -> {})

This method will start the JavaFX runtime and call the supplied Runnable when the startup has been completed successfully. In our case, we don’t call a Runnable immediately; hence, we pass an empty Runnable. Once this method returns, the JavaFX Application Thread is created, and we can use Platform.runLater() statements to create or modify the SceneGraph, similar to how we should do it with JavaFX applications where the runtime is started by the JavaFX launcher.

We first compile the class with Maven, using
mvn compile
If you prefer to use a command line compilation, that is easily done by the following command:
javac -p /opt/javafx-sdk-13/lib --add-modules javafx.controls src/main/java/org/modernclient/StandAlone.java
where /opt/javafx-sdk-13 should be replaced with the location where you downloaded the JavaFX 13 SDK.
We run it with
mvn exec:exec
or, if you used the command line compilation and have the classes compiled to the same directory as the sources:
java -p /opt/javafx-sdk-13/lib --add-modules javafx.controls -cp src/main/java/ org.modernclient.StandAlone

The result is shown in the following:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figl_HTML.jpg
In order for Maven to correctly launch this application, we had to supply the modulepath and the required modules (javafx.controls) to the pom.xml. The relevant part of the pom.xml file is shown in the following:
<plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
            <executable>java</executable>
            <longModulepath>false</longModulepath>
            <arguments>
                <argument>--module-path</argument>
                <classpath />
                <argument>--add-modules</argument>
                <argument>javafx.controls</argument>
                <argument>-classpath</argument>
                <classpath />
                <argument>org.modernclient.StandAlone</argument>
            </arguments>
        </configuration>
      </plugin>

In this snippet, we configured the exec task to call the Java command, but we don’t use the default launcher that Maven would otherwise use. Instead, we manually tell Java that it should take the module-path from the classpath (which includes the dependency javafx-controls) and add the javafx.controls module.

Running this application can also be done on the command line. For example, if the JavaFX SDK is installed at /opt/javafx-sdk-11.0.2, the following works:
java -p /opt/javafx-sdk-11.0.2/lib/ --add-modules javafx.controls -cp target/classes org.modernclient.StandAlone

JavaFX Applications in JShell

We can now run the code in JShell. As we mentioned before, JShell is using a JVM for its execution. We can simply enter the same commands in JShell, and the output will immediately tell us what happened.

If we assume the JavaFX SDK is installed in /opt/javafx-sdk-11.0.2, the following command launches JShell, sets the module-path correctly, and adds the javafx.controls module:
jshell --module-path /opt/javafx-sdk-11.0.1/lib/ --add-modules javafx.controls

We can now enter the commands that ultimately make a JavaFX application, starting with the imports:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figm_HTML.jpg

Note that we didn’t add a package declaration. This touches one of the few differences between regular Java applications and JShell code. Packages are created with the goal of exposing functionality and use that in other components, in other libraries. The JShell concept is to provide a stand-alone, interactive, and self-contained environment; hence, it does not make sense to expose packages.

Now that the imports are added, we can create the showHello() method. We will first use JShell in a less efficient way, but in the next section, we will show how this can be done in a much more productive way.

For now, we simply copy-paste the showHello method. This gives the following output:

../images/468104_1_En_14_Chapter/468104_1_En_14_Fign_HTML.jpg

The JShell editor allows for statements or declarations to be split over many lines. The parser recognizes that after the first line is entered, more content is needed. Hence, it will only process the method declaration after we completed it. It will detect the final closing curly bracket.

Note that we got one warning, telling us that the static keyword is ignored. In JShell, all top-level declarations are static so that keyword is useless in this context. This is another difference between regular applications and code living in the JShell context.

Now that the method is declared, we can call it. This is simply done by calling showHello() from the JShell prompt:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figo_HTML.jpg

The statement returns immediately, and the same image as shown in the preceding screenshot.

JavaFX Libraries in JShell

While the example in the previous section works, it is very verbose, requires manual typing or copy-pasting, and does not really allow for fast prototyping. The real benefit for the combination of JavaFX and JShell comes from libraries and functions that provide simple things that can then be called from JShell statements . Typically, the functions are created in Java files, compiled, and made available to JShell. This is very similar to how the ND4J libraries are made available to JShell.

As an example, we create a simple function that creates a JavaFX chart containing some scattered data.

We write the function as a regular Java function, as shown in the code in the following:
package org.modernclient;
import javafx.application.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.stage.Stage;
public class Plot {
    public static void scatter(double[] x, double[] y, String title) {
        Platform.startup(() -> {});
        Platform.setImplicitExit(false);
        Platform.runLater( () -> {
        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        ScatterChart chart = new ScatterChart(xAxis, yAxis);
        ObservableList<XYChart.Series> chartData = FXCollections.observableArrayList();
        XYChart.Series<Number, Number> series = new XYChart.Series<>();
        ObservableList<Data<Number, Number>> data = FXCollections.observableArrayList();
        for (int i = 0; i < x.length; i++) {
            Data<Number, Number> d = new Data<>(x[i],y[i]);
            data.add(d);
        }
        series.setData(data);
        chartData.setAll(series);
        chart.setData(chartData);
        Scene s = new Scene(chart, 400, 400);
        Stage stage = new Stage();
        stage.setTitle(title);
        stage.setScene(s);
        stage.show();
        });
    }
    public static void main(String[] args) {
        double[] x = new double[]{0.,1.,2.};
        double[] y = new double[]{0.,10.,16.};
        scatter(x, y, "plot");
    }
}

In this code, we defined a scatter function that takes three arguments: an array of doubles containing the x coordinates, an array of doubles containing the y coordinates, and a title for the chart. The scatter function initializes the JavaFX runtime, and then it creates a ScatterChart containing the data provided by the arguments. The chart is added to the Scene and rendered on the Stage.

We added a main function to this class so that we can test the function using regular Java invocations. In this main function, we pass three data points (hence three values for the x array and three values for the y array), and we supply the title “plot.”

To get the UI in Figure 14-6 we will compile and run the application using the same approach as before:
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig6_HTML.jpg
Figure 14-6

Results of the running application

Mvn compile exec:exec

We will now make this function available to JShell and call it from there.

We start JShell using the Maven plugin we used before:
mvn com.github.johnpoth:jshell-maven-plugin:1.1:run

Make sure to invoke this command in the directory that contains the pom.xml file for the plot-code. This will add the compiled classes and dependencies to the class and module path.

When JShell is started, the following message is shown:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figp_HTML.jpg
We first import the Plot class by entering
Import org.modernclient.Plot;

We also create two arrays containing the x and y values for the data points we want to visualize:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figq_HTML.jpg
Finally, to create the graph shown in Figure 14-7 we call the scatter function as follows:
../images/468104_1_En_14_Chapter/468104_1_En_14_Fig7_HTML.jpg
Figure 14-7

The new plot graph, resulting from the preceding code

Plot.scatter(c, d, "plot from jshell");

One of the nice features of JShell is that there are a number of built-in commands that allow to work more efficient with the editor. The /list command for example shows the commands that have been executed in order. After we successfully applied the previous steps, the result of the /list command is shown in the following:

../images/468104_1_En_14_Chapter/468104_1_En_14_Figr_HTML.jpg

This screenshot clearly shows that it is much easier to call predefined functions in JShell than to declare all functions manually in JShell. As a rule of thumb, if you expect your function to be modified very often, you can declare it in JShell. But if you mainly want to evaluate functions and inspect the results, declaring them in classes and importing them in the JShell environment is more appropriate.

Conclusion

The combination of Java and JavaFX provides a solid foundation for scientific work. Java itself is already very performant and highly scalable. Using JShell and ND4J, it becomes possible for (data) scientists to work on research projects that require flexible manipulation of data, using familiar linear algebra routines.

Often, interactive visualization during the design process shortens the development cycle, and improves the quality of the result. Bringing the JavaFX visualization in the picture during the research phase allows for real-time visualization and highly interactive scientific applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.113.67